diff --git a/ansible/library/sonic_basic_facts.py b/ansible/library/sonic_basic_facts.py new file mode 100644 index 00000000000..f5f65f9c1d0 --- /dev/null +++ b/ansible/library/sonic_basic_facts.py @@ -0,0 +1,482 @@ +#!/usr/bin/python +""" +Ansible module for gathering basic facts for a SONiC host. + +Comparing with the dut_basic_facts module, this module is called by __init__ of SonicHost +defined in tests/common/devices/sonic.py to gather facts when creating a SonicHost object. + +The dut_basic_facts module is used by the conditional mark plugin to gather facts to be used +while evaluating the conditions of conditional mark plugin. +""" + +import json +import os +import yaml + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text +from sonic_py_common import device_info +from swsscommon.swsscommon import ConfigDBConnector + +DOCUMENTATION = ''' +--- +module: sonic_basic_facts +author: Xin Wang (xiwang5@microsoft.com) +short_description: Retrieve basic facts from SONiC host. +description: + - Retrieve basic facts from SONiC host. This module should only be applied to a SONiC device. +options: + N/A +''' + +EXAMPLES = ''' +# Gather SONiC basic facts +- name: Gathering SONiC basic facts + sonic_basic_facts: + +Example output: +{ + "ansible_facts": { + "basic_facts": { + "asic_count": 1, + "asic_type": "vs", + "hwsku": "Force10-S6100", + "mgmt_interface": [ + "10.250.0.114/24", + "fec0::ffff:afa:e/64" + ], + "modular_chassis": false, + "num_asic": 1, + "platform": "x86_64-kvm_x86_64-r0", + "platform_asic": "vs", + "router_mac": "22:84:f5:7c:ed:84", + "router_subtype": "", + "router_type": "ToRRouter", + "switch_type": "" + }, + "discovered_interpreter_python": "/usr/bin/python3.11", + "features": { + "bgp": { + "auto_restart": "enabled", + "check_up_status": "false", + "delayed": "False", + "has_global_scope": "False", + "has_per_asic_scope": "True", + "high_mem_alert": "disabled", + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "bmp": { + "auto_restart": "enabled", + "check_up_status": "false", + "delayed": "False", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "set_owner": "local", + "state": "enabled", + "support_syslog_rate_limit": "false" + }, + "database": { + "auto_restart": "always_enabled", + "delayed": "False", + "has_global_scope": "True", + "has_per_asic_scope": "True", + "high_mem_alert": "disabled", + "state": "always_enabled", + "support_syslog_rate_limit": "true" + }, + "dhcp_relay": { + "auto_restart": "enabled", + "check_up_status": "False", + "delayed": "False", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "set_owner": "local", + "state": "enabled", + "support_syslog_rate_limit": "True" + }, + "dhcp_server": { + "auto_restart": "enabled", + "check_up_status": "False", + "delayed": "False", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "set_owner": "local", + "state": "disabled", + "support_syslog_rate_limit": "False" + }, + "eventd": { + "auto_restart": "enabled", + "delayed": "False", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "frr_bmp": { + "auto_restart": "disabled", + "check_up_status": "false", + "delayed": "False", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "set_owner": "local", + "state": "enabled", + "support_syslog_rate_limit": "false" + }, + "gbsyncd": { + "auto_restart": "enabled", + "delayed": "False", + "has_global_scope": "False", + "has_per_asic_scope": "True", + "high_mem_alert": "disabled", + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "gnmi": { + "auto_restart": "enabled", + "delayed": "True", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "lldp": { + "auto_restart": "enabled", + "delayed": "True", + "has_global_scope": "True", + "has_per_asic_scope": "True", + "high_mem_alert": "disabled", + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "macsec": { + "auto_restart": "enabled", + "check_up_status": "False", + "delayed": "False", + "has_global_scope": "False", + "has_per_asic_scope": "True", + "high_mem_alert": "disabled", + "set_owner": "local", + "state": "disabled", + "support_syslog_rate_limit": "True" + }, + "mgmt-framework": { + "auto_restart": "enabled", + "delayed": "True", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "mux": { + "auto_restart": "enabled", + "delayed": "False", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "state": "always_disabled", + "support_syslog_rate_limit": "true" + }, + "nat": { + "auto_restart": "enabled", + "delayed": "False", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "state": "disabled", + "support_syslog_rate_limit": "true" + }, + "pmon": { + "auto_restart": "enabled", + "check_up_status": "false", + "delayed": "True", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "radv": { + "auto_restart": "enabled", + "delayed": "False", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "sflow": { + "auto_restart": "enabled", + "delayed": "True", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "state": "disabled", + "support_syslog_rate_limit": "true" + }, + "snmp": { + "auto_restart": "enabled", + "delayed": "True", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "high_mem_alert": "disabled", + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "swss": { + "auto_restart": "enabled", + "check_up_status": "false", + "delayed": "False", + "has_global_scope": "False", + "has_per_asic_scope": "True", + "high_mem_alert": "disabled", + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "syncd": { + "auto_restart": "enabled", + "delayed": "False", + "has_global_scope": "False", + "has_per_asic_scope": "True", + "high_mem_alert": "disabled", + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "teamd": { + "auto_restart": "enabled", + "delayed": "False", + "has_global_scope": "False", + "has_per_asic_scope": "True", + "high_mem_alert": "disabled", + "state": "enabled", + "support_syslog_rate_limit": "true" + }, + "telemetry": { + "delayed": "False", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "state": "disabled" + } + }, + "versions": { + "asic_subtype": "vs", + "asic_type": "vs", + "branch": "master", + "build_date": "Tue Oct 14 12:50:35 UTC 2025", + "build_number": 963173, + "build_version": "master.963173-0d6aace3b", + "built_by": "azureuser@5e07c093c000000", + "commit_id": "0d6aace3b", + "debian_version": "12.12", + "kernel_version": "6.1.0-29-2-amd64", + "libswsscommon": "1.0.0", + "release": "none", + "secure_boot_image": "no", + "sonic-ctrmgrd-rs": "1.0.0", + "sonic_os_version": 12, + "sonic_utilities": 1.2 + } + }, + "changed": false +} +''' + + +def sanitize_value(value): + """ + Convert AnsibleUnsafeText to regular Python strings recursively. + + Args: + value: Any value that may contain AnsibleUnsafeText strings. + + Returns: + The same value structure with all strings converted to native Python strings. + """ + if isinstance(value, dict): + return {k: sanitize_value(v) for k, v in value.items()} + elif isinstance(value, list): + return [sanitize_value(item) for item in value] + elif isinstance(value, str): + return to_text(value, errors='surrogate_or_strict') + else: + return value + + +def read_json_file(file_path): + """Read and parse a JSON file, returning empty dict on error. + Converts all string values to native Python strings.""" + if not os.path.exists(file_path): + return {} + + try: + with open(file_path, 'r') as f: + data = json.load(f) + return sanitize_value(data) + except (IOError, json.JSONDecodeError): + return {} + + +def read_yaml_file(file_path): + """Read and parse a YAML file, returning empty dict on error. + Converts all string values to native Python strings.""" + if not os.path.exists(file_path): + return {} + + try: + with open(file_path, 'r') as f: + data = yaml.safe_load(f) or {} + return sanitize_value(data) + except (IOError, yaml.YAMLError): + return {} + + +def read_text_file(file_path): + """Read a text file and return its content stripped, or None on error. + Converts the string to native Python string.""" + if not os.path.exists(file_path): + return None + + try: + with open(file_path, 'r') as f: + content = f.read().strip() + return to_text(content, errors='surrogate_or_strict') if content else None + except IOError: + return None + + +def get_basic_facts(): + """ + Get basic platform facts from device_info. + + Returns: + dict: Platform information including platform, hwsku, asic_type, and asic_count. + Example: {'platform': 'x86_64-kvm_x86_64-r0', 'hwsku': 'Force10-S6000', + 'asic_type': 'vs', 'asic_count': 1} + """ + basic_facts = device_info.get_platform_info() + + # Augment with platform.json if available + platform = basic_facts.get('platform', '') + if platform: + platform_json_path = f"/usr/share/sonic/device/{platform}/platform.json" + platform_json = read_json_file(platform_json_path) + basic_facts.update(platform_json) + + return basic_facts + + +def is_chassis(asic_type): + """ + Check if the device is a modular chassis. + + Args: + asic_type (str): The ASIC type of the device. + + Returns: + bool: True if device is a modular chassis, False otherwise. + """ + try: + if asic_type == 'vs': + return device_info.is_chassis() + + import sonic_platform.platform as platform_module + chassis = platform_module.Platform().get_chassis() + return chassis.is_modular_chassis() + except Exception: + return False + + +def get_platform_asic(platform): + """ + Get the platform ASIC type from platform_asic file. + + Args: + platform (str): The platform name. + + Returns: + str or None: The ASIC type if found, None otherwise. + """ + if not platform: + return None + + platform_asic_path = f"/usr/share/sonic/device/{platform}/platform_asic" + return read_text_file(platform_asic_path) + + +def get_sonic_version(): + """ + Get SONiC version information from sonic_version.yml. + + Returns: + dict: Version information from sonic_version.yml. + """ + return read_yaml_file('/etc/sonic/sonic_version.yml') + + +def gather_config_db_facts(config_db, basic_facts): + """ + Gather facts from ConfigDB and update basic_facts. + + Args: + config_db (ConfigDBConnector): Connected ConfigDB instance. + basic_facts (dict): Dictionary to update with ConfigDB facts. + """ + # Get device metadata + # Example: {"localhost": {"mac": "...", "hostname": "...", "type": "LeafRouter", ...}} + metadata = config_db.get_table('DEVICE_METADATA') + localhost_metadata = metadata.get('localhost', {}) + + # Update basic facts with metadata + basic_facts['num_asic'] = basic_facts.get('asic_count', 1) + basic_facts['router_mac'] = localhost_metadata.get('mac', '') + basic_facts['modular_chassis'] = is_chassis(basic_facts.get('asic_type', '')) + basic_facts['switch_type'] = localhost_metadata.get('switch_type', '') + basic_facts['router_type'] = localhost_metadata.get('type', '') + basic_facts['router_subtype'] = localhost_metadata.get('subtype', '') + + # Extract management interface addresses + # Example: {('eth0', '10.250.0.52/24'): {'gwaddr': '10.250.0.1'}} + mgmt_interface = config_db.get_table('MGMT_INTERFACE') + basic_facts['mgmt_interface'] = [key[1] for key in mgmt_interface.keys()] + + # Add platform ASIC if available + platform_asic = get_platform_asic(basic_facts.get('platform', '')) + if platform_asic: + basic_facts['platform_asic'] = platform_asic + + +def main(): + """Main entry point for the Ansible module.""" + module = AnsibleModule(argument_spec=dict(), supports_check_mode=False) + + try: + # Gather basic platform facts + basic_facts = get_basic_facts() + + # Connect to ConfigDB and gather additional facts + config_db = ConfigDBConnector() + config_db.connect() + gather_config_db_facts(config_db, basic_facts) + + # Prepare results + results = { + 'basic_facts': basic_facts, + 'versions': get_sonic_version(), + 'features': config_db.get_table('FEATURE') + } + + module.exit_json(ansible_facts=results) + + except Exception as e: + module.fail_json(msg=str(e)) + + +if __name__ == '__main__': + main() diff --git a/tests/common/devices/multi_asic.py b/tests/common/devices/multi_asic.py index 9e715b947c8..a6d3b953e44 100644 --- a/tests/common/devices/multi_asic.py +++ b/tests/common/devices/multi_asic.py @@ -69,7 +69,7 @@ def critical_services_tracking_list(self): active_asics = self.asics if self.sonichost.is_supervisor_node(): service_list.append("lldp") - if self.get_facts()['asic_type'] != 'vs': + if self.facts['asic_type'] != 'vs': active_asics = [] sonic_db_cli_out = \ self.command("sonic-db-cli CHASSIS_STATE_DB keys \"CHASSIS_FABRIC_ASIC_TABLE|asic*\"") @@ -81,40 +81,38 @@ def critical_services_tracking_list(self): active_asics = [] service_list += self._DEFAULT_SERVICES - config_facts = self.config_facts(host=self.hostname, source="running")['ansible_facts'] # NOTE: Add mux to critical services for dualtor if ( - "DEVICE_METADATA" in config_facts and - "localhost" in config_facts["DEVICE_METADATA"] and - "subtype" in config_facts["DEVICE_METADATA"]["localhost"] and - config_facts["DEVICE_METADATA"]["localhost"]["subtype"] == "DualToR" and - "mux" in config_facts["FEATURE"] and config_facts["FEATURE"]["mux"]["state"] == "enabled" + self.facts['router_subtype'] == 'DualToR' and + self.facts.get('features', {}).get('mux', {}).get('state', '') == 'enabled' ): service_list.append("mux") - if "dhcp_relay" in config_facts["FEATURE"] and config_facts["FEATURE"]["dhcp_relay"]["state"] == "enabled": + _features = self.facts.get('features', {}) + + if _features.get('dhcp_relay', {}).get('state', '') == 'enabled': service_list.append("dhcp_relay") - if "dhcp_server" in config_facts["FEATURE"] and config_facts["FEATURE"]["dhcp_server"]["state"] == "enabled": + if _features.get('dhcp_server', {}).get('state', '') == 'enabled': service_list.append("dhcp_server") - if config_facts['DEVICE_METADATA']['localhost'].get('switch_type', '') == 'dpu' and 'snmp' in service_list: + if self.facts.get('switch_type', '') == 'dpu' and 'snmp' in service_list: service_list.remove('snmp') # Update the asic service based on feature table state and asic flag for service in list(self.sonichost.DEFAULT_ASIC_SERVICES): - if service == 'teamd' and config_facts['DEVICE_METADATA']['localhost'].get('switch_type', '') == 'dpu': + if service == 'teamd' and self.facts.get('switch_type', '') == 'dpu': logger.info("Removing teamd from default services for switch_type DPU") self.sonichost.DEFAULT_ASIC_SERVICES.remove(service) continue - if service not in config_facts['FEATURE']: + if service not in _features: self.sonichost.DEFAULT_ASIC_SERVICES.remove(service) continue - if config_facts['FEATURE'][service]['has_per_asic_scope'] == "False": + if _features.get(service, {}).get('has_per_asic_scope', '') == "False": self.sonichost.DEFAULT_ASIC_SERVICES.remove(service) - if config_facts['FEATURE'][service]['state'] == "disabled": + if _features.get(service, {}).get('state', '') == "disabled": self.sonichost.DEFAULT_ASIC_SERVICES.remove(service) - if not self.get_facts().get("modular_chassis"): + if not self.facts.get("modular_chassis"): service_list.append("lldp") for asic in active_asics: diff --git a/tests/common/devices/sonic.py b/tests/common/devices/sonic.py index a759f7b3e6a..4dd65d4df0b 100644 --- a/tests/common/devices/sonic.py +++ b/tests/common/devices/sonic.py @@ -17,12 +17,10 @@ from tests.common.devices.base import AnsibleHostBase from tests.common.devices.constants import ACL_COUNTERS_UPDATE_INTERVAL_IN_SEC from tests.common.helpers.dut_utils import is_supervisor_node, is_macsec_capable_node -from tests.common.str_utils import str2bool from tests.common.utilities import get_host_visible_vars from tests.common.cache import cached from tests.common.helpers.constants import DEFAULT_ASIC_ID, DEFAULT_NAMESPACE from tests.common.helpers.platform_api.chassis import is_inband_port -from tests.common.helpers.parallel import parallel_run_threaded from tests.common.errors import RunAnsibleModuleFail from tests.common import constants @@ -84,24 +82,29 @@ def __init__(self, ansible_adhoc, hostname, } self.host.options['variable_manager'].extra_vars.update(evars) - self._facts = self._gather_facts() - self._os_version = self._get_os_version() + _gathered_facts = self._gather_facts() - device_metadata = self.get_running_config_facts().get('DEVICE_METADATA', {}).get('localhost', {}) - device_type = device_metadata.get('type') - device_subtype = device_metadata.get('subtype') - if (device_type == 'UpperSpineRouter') or (device_subtype in ['UpstreamLC', 'DownstreamLC']): - self.DEFAULT_ASIC_SERVICES.append("macsec") + self._facts = _gathered_facts.get('basic_facts', {}) + self._facts['features'] = _gathered_facts.get('features', {}) + + self.is_multi_asic = True if self.facts["num_asic"] > 1 else False + + self._os_version = _gathered_facts.get('versions', {}).get('build_version', '') + _sonic_release = _gathered_facts.get('versions', {}).get('release', '') + if not _sonic_release: + _sonic_release = self._os_version.split('.')[0][0:6] + self._sonic_release = _sonic_release + self._kernel_version = _gathered_facts.get('versions', {}).get('kernel_version', '').split('-')[0] - feature_status = self.get_feature_status(disable_cache=False) + router_type = self._facts.get('router_type', '') + router_subtype = self._facts.get('router_subtype', '') + if (router_type == 'UpperSpineRouter') or (router_subtype in ['UpstreamLC', 'DownstreamLC']): + self.DEFAULT_ASIC_SERVICES.append("macsec") # Append gbsyncd only for non-VS to avoid pretest check for gbsyncd # e.g. in test_feature_status, test_disable_rsyslog_rate_limit - gbsyncd_enabled = 'gbsyncd' in feature_status[0].keys() and feature_status[0]['gbsyncd'] == 'enabled' + gbsyncd_enabled = self._facts.get('features', {}).get('gbsyncd', {}).get('state', 'disabled') == 'enabled' if gbsyncd_enabled and self.facts["asic_type"] != "vs": self.DEFAULT_ASIC_SERVICES.append("gbsyncd") - self._sonic_release = self._get_sonic_release() - self.is_multi_asic = True if self.facts["num_asic"] > 1 else False - self._kernel_version = self._get_kernel_version() def __str__(self): return ''.format(self.hostname) @@ -117,14 +120,8 @@ def facts(self): Returns: dict: A dictionary containing the device platform information. - For example: - { - "platform": "x86_64-arista_7050_qx32s", - "hwsku": "Arista-7050-QX-32S", - "asic_type": "broadcom", - "num_asic": 1, - "router_mac": "52:54:00:f0:ac:9d", - } + Refer to docstring of ansible/library/sonic_basic_facts.py. + The self._facts has the value of res['ansible_facts']['basic_facts'] """ return self._facts @@ -201,188 +198,18 @@ def reset_critical_services_tracking_list(self, service_list): def _gather_facts(self): """ Gather facts about the platform for this SONiC device. - """ - facts = self._get_platform_info() - - results = parallel_run_threaded( - [ - lambda: self._get_asic_count(facts["platform"]), - self._get_router_mac, - lambda: self._get_modular_chassis(facts["asic_type"]), - self._get_mgmt_interface, - self._get_switch_type, - self._get_router_type, - self.get_asics_present_from_inventory, - lambda: self._get_platform_asic(facts["platform"]) - ], - timeout=180, - thread_count=5 - ) - - facts["num_asic"] = results[0] - facts["router_mac"] = results[1] - facts["modular_chassis"] = str2bool(results[2]) - facts["mgmt_interface"] = results[3] - facts["switch_type"] = results[4] - facts["router_type"] = results[5] - - facts["asics_present"] = results[6] if len(results[6]) != 0 else list(range(facts["num_asic"])) - if results[7]: - facts["platform_asic"] = results[7] + Refer to docstring of ansible/library/sonic_basic_facts.py for example output of the sonic_basic_facts module. + """ + facts = self.sonic_basic_facts().get('ansible_facts', {}) + asics_present = self.get_asics_present_from_inventory() + facts['basic_facts']['asics_present'] = asics_present \ + if len(asics_present) != 0 \ + else list(range(facts['basic_facts']['num_asic'])) logging.debug("Gathered SonicHost facts: %s" % json.dumps(facts)) return facts - def _get_mgmt_interface(self): - """ - Gets the IPs of management interface - Output example - - admin@ARISTA04T1:~$ show management_interface address - Management IP address = 10.250.0.54/24 - Management Network Default Gateway = 10.250.0.1 - Management IP address = 10.250.0.59/24 - Management Network Default Gateway = 10.250.0.1 - - """ - show_cmd_output = self.shell("show management_interface address", module_ignore_errors=True) - mgmt_addrs = [] - for line in show_cmd_output["stdout_lines"]: - addr = re.match(r"Management IP address = (\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3})\/\d+", line) - if addr: - mgmt_addrs.append(addr.group(1)) - return mgmt_addrs - - def _get_modular_chassis(self, asic_type): - if asic_type == 'vs': - out = self.shell( - "python3 -c \"import sonic_py_common.device_info as P; \ - print(P.is_chassis()); exit()\"", - module_ignore_errors=True) - res = "False" if out["failed"] else out["stdout"] - return res - - py_res = self.shell("python -c \"import sonic_platform\"", module_ignore_errors=True) - if py_res["failed"]: - out = self.shell( - "python3 -c \"import sonic_platform.platform as P; \ - print(P.Platform().get_chassis().is_modular_chassis()); exit()\"", - module_ignore_errors=True) - else: - out = self.shell( - "python -c \"import sonic_platform.platform as P; \ - print(P.Platform().get_chassis().is_modular_chassis()); exit()\"", - module_ignore_errors=True) - res = "False" if out["failed"] else out["stdout"] - return res - - def _get_asic_count(self, platform): - """ - Gets the number of asics for this device. - """ - num_asic = 1 - asic_conf_file_path = os.path.join("/usr/share/sonic/device", platform, "asic.conf") - try: - output = self.shell("cat {}".format(asic_conf_file_path))["stdout_lines"] - logging.debug(output) - - for line in output: - key, value = line.split("=") - if key.strip().upper() == "NUM_ASIC": - num_asic = value.strip() - break - - logging.debug("num_asic = %s" % num_asic) - - return int(num_asic) - except Exception: - return int(num_asic) - - def _get_router_mac(self): - return self.command("sonic-cfggen -d -v 'DEVICE_METADATA.localhost.mac'")["stdout_lines"][0].encode().decode( - "utf-8").lower() - - def _get_switch_type(self): - try: - return self.command("sonic-cfggen -d -v 'DEVICE_METADATA.localhost.switch_type'")["stdout_lines"][0]\ - .encode().decode("utf-8").lower() - except Exception: - return '' - - def _get_router_type(self): - try: - return self.command("sonic-cfggen -d -v 'DEVICE_METADATA.localhost.type'")["stdout_lines"][0] \ - .encode().decode("utf-8").lower() - except Exception: - return '' - - def _get_platform_info(self): - """ - Gets platform information about this SONiC device. - """ - - platform_info = self.command("show platform summary")["stdout_lines"] - result = {} - for line in platform_info: - if line.startswith("Platform:"): - result["platform"] = line.split(":")[1].strip() - elif line.startswith("HwSKU:"): - result["hwsku"] = line.split(":")[1].strip() - elif line.startswith("ASIC:"): - result["asic_type"] = line.split(":")[1].strip() - - if result["platform"]: - platform_file_path = os.path.join("/usr/share/sonic/device", result["platform"], "platform.json") - - try: - out = self.command("cat {}".format(platform_file_path)) - platform_info = json.loads(out["stdout"]) - for key, value in list(platform_info.items()): - result[key] = value - - except Exception: - # if platform.json does not exist, then it's not added currently for certain platforms - # eventually all the platforms should have the platform.json - logging.debug("platform.json is not available for this platform, " + - "DUT facts will not contain complete platform information.") - - return result - - @cached(name='os_version') - def _get_os_version(self): - """ - Gets the SONiC OS version that is running on this device. - """ - - output = self.command("sonic-cfggen -y /etc/sonic/sonic_version.yml -v build_version") - return output["stdout_lines"][0].strip() - - @cached(name='sonic_release') - def _get_sonic_release(self): - """ - Gets the SONiC Release that is running on this device. - E.g. 202106, 202012, ... - if the release is master, then return none - """ - - output = self.command("sonic-cfggen -y /etc/sonic/sonic_version.yml -v release") - if len(output['stdout_lines']) == 0: - # get release from OS version - if self.os_version: - return self.os_version.split('.')[0][0:6] - return 'none' - return output["stdout_lines"][0].strip() - - @cached(name='kernel_version') - def _get_kernel_version(self): - """ - Gets the SONiC kernel version - :return: - """ - output = self.command('uname -r') - return output["stdout"].split('-')[0] - def get_service_props(self, service, props=["ActiveState", "SubState"]): """ @summary: Use 'systemctl show' command to get detailed properties of a service. By default, only get