Files
test/keywords/ptp/setup/ptp_setup_reader.py
abhinav_ayyapasetti 1fabfc59ee pylint fix
Description:
- Added data types to function arguments and return values.
- Added blank lines between summary and description in docstrings to match style guidelines.
- Removed extra blank lines after docstrings.
- Added a missing import for SSHConnection in sma_keywords.py.
- Capitalized the first word in each docstring to comply with style guide rules.
- Improved docstring for PTP4LStatusObject constructor with detailed attribute descriptions

Change-Id: Idada0b0b0c3f895a16f4b439beaaaf071597a16a

Change-Id: I8e7756d32eb56a2aa85b277a91b26cc6280d1c56
Signed-off-by: aabhinav <ayyapasetti.abhinav@windriver.com>
2025-07-09 19:38:14 +00:00

212 lines
9.4 KiB
Python

import copy
from typing import Any, Dict, List, Optional, Tuple
import json5
from jinja2 import Template
from config.configuration_manager import ConfigurationManager
from framework.resources.resource_finder import get_stx_resource_path
from keywords.base_keyword import BaseKeyword
from keywords.ptp.setup.object.ptp_setup import PTPSetup
class PTPSetupKeywords(BaseKeyword):
"""
This class is responsible for reading ptp_setup files and matching them with the ptp config.
"""
def generate_ptp_setup_from_template(self, template_file_location: str) -> PTPSetup:
"""
This function will read the template_file specified and will replace values based on the ptp_config.
Args:
template_file_location (str): Path in the repo to the Template JSON5 file. e.g. 'resources/ptp/setup/ptp_setup_template.json5'
Returns:
PTPSetup: An object representing the setup.
"""
# Load the Template JSON5 file.
with open(template_file_location, "r") as template_file:
json5_template = template_file.read()
ptp_default_status_values_file_path = get_stx_resource_path("resources/ptp/ptp_default_status_values.json5")
with open(ptp_default_status_values_file_path, "r") as ptp_default_status_values_template_file:
ptp_default_status_values_template = json5.load(ptp_default_status_values_template_file)
# Build a replacement dictionary from the PTP Config
ptp_config = ConfigurationManager.get_ptp_config()
replacement_dictionary = ptp_config.get_all_hosts_dictionary()
# Update lab_topology dict with ptp_default_status_values
replacement_dictionary.update(ptp_default_status_values_template)
# Render the JSON5 file by replacing the tokens.
template = Template(json5_template)
rendered_json_string = template.render(replacement_dictionary)
json_data = json5.loads(rendered_json_string)
# Turn the JSON Data into a ptp_setup object.
ptp_setup = PTPSetup(json_data)
return ptp_setup
def filter_and_render_ptp_config(self, template_file_location: str, selected_instances: List[Tuple[str, str, List[str]]], custom_expected_dict_template: Optional[str] = None, expected_dict_overrides: Optional[Dict[str, Any]] = None) -> PTPSetup:
"""
Filters and renders a PTP configuration from a JSON5 template based on selected instances.
This function is useful for generating a partial configuration from a complete PTP setup,
based on specific instance names, hostnames, and interfaces. It also allows:
- Overriding expected_dict via a custom Jinja2 template string
- Applying deep overrides to specific values (e.g., changing `clock_class`)
Args:
template_file_location (str): Path to the JSON5 template file.
selected_instances (List[Tuple[str, str, List[str]]]):
List of tuples, where each tuple contains:
- ptp_instance_name (str)
- hostname (str)
- list of associated interface names (List[str])
custom_expected_dict_template (Optional[str]):
Jinja2-formatted string representing an expected_dict override. If provided,
it replaces the auto-filtered expected_dict.
expected_dict_overrides (Optional[Dict[str, Any]]):
A dictionary of specific overrides to apply on the generated or provided expected_dict.
Supports nested structure (e.g. overriding grandmaster_settings -> clock_class).
Returns:
PTPSetup: A PTPSetup object containing the filtered configuration.
Example:
filter_and_render_ptp_config(
template_file_location="resources/ptp/setup/ptp_setup_template.json5",
selected_instances=[("ptp4", "controller-1", ["ptp4if1"])],
expected_dict_overrides={
"ptp4l": [
{
"name": "ptp4",
"controller-1": {
"grandmaster_settings": {
"clock_class": 165
}
}
}
]
}
)
"""
# Load and render the JSON5 template
with open(template_file_location, "r") as template_file:
json5_template = template_file.read()
ptp_defaults_path = get_stx_resource_path("resources/ptp/ptp_default_status_values.json5")
with open(ptp_defaults_path, "r") as defaults_file:
ptp_defaults = json5.load(defaults_file)
ptp_config = ConfigurationManager.get_ptp_config()
render_context = ptp_config.get_all_hosts_dictionary()
render_context.update(ptp_defaults)
# Render main config template
rendered_config = Template(json5_template).render(render_context)
ptp_config_dict = json5.loads(rendered_config)
# Optionally render custom expected_dict
custom_expected_dict = None
if custom_expected_dict_template:
rendered_custom_expected = Template(custom_expected_dict_template).render(render_context)
custom_expected_dict = json5.loads(rendered_custom_expected)
filtered_json = {"ptp_instances": {"ptp4l": []}, "ptp_host_ifs": [], "expected_dict": {"ptp4l": []}}
ptp_selection = {}
all_required_ifaces = set()
for ptp_name, hostname, iface_list in selected_instances:
if ptp_name not in ptp_selection:
ptp_selection[ptp_name] = {}
ptp_selection[ptp_name][hostname] = iface_list
all_required_ifaces.update(iface_list)
# Filter ptp_instances.ptp4l
for instance in ptp_config_dict.get("ptp_instances", {}).get("ptp4l", []):
name = instance.get("name")
if name in ptp_selection:
hosts = ptp_selection[name]
selected_ifaces = [iface for iface_list in hosts.values() for iface in iface_list]
filtered_json["ptp_instances"]["ptp4l"].append(
{
"name": name,
"instance_hostnames": list(hosts.keys()),
"instance_parameters": instance.get("instance_parameters", ""),
"ptp_interface_names": selected_ifaces,
}
)
# Filter ptp_host_ifs
for iface in ptp_config_dict.get("ptp_host_ifs", []):
if iface.get("name") in all_required_ifaces:
filtered_json["ptp_host_ifs"].append(iface)
# Use custom expected_dict if provided
if custom_expected_dict:
filtered_json["expected_dict"]["ptp4l"] = custom_expected_dict.get("ptp4l", [])
return PTPSetup(filtered_json)
# Auto-generate expected_dict by filtering
for expected_instance in ptp_config_dict.get("expected_dict", {}).get("ptp4l", []):
name = expected_instance.get("name")
if name not in ptp_selection:
continue
filtered_instance = {"name": name}
for hostname, _ in ptp_selection[name].items():
instance_data = expected_instance.get(hostname)
if not instance_data:
continue
filtered_instance[hostname] = {key: instance_data.get(key) for key in ["parent_data_set", "time_properties_data_set", "grandmaster_settings", "port_data_set"]}
filtered_json["expected_dict"]["ptp4l"].append(filtered_instance)
# Apply single-value overrides (like clock_class)
if expected_dict_overrides:
for override in expected_dict_overrides.get("ptp4l", []):
override_name = override.get("name")
for inst in filtered_json["expected_dict"]["ptp4l"]:
if inst.get("name") == override_name:
for hostname, host_data in override.items():
if hostname == "name":
continue
inst.setdefault(hostname, {})
inst[hostname] = self.deep_merge(inst[hostname], host_data)
return PTPSetup(filtered_json)
def deep_merge(self, dest: Dict[str, Any], src: Dict[str, Any]) -> Dict[str, Any]:
"""
Recursively merges the contents of `src` into `dest`.
If both `dest` and `src` contain a value for the same key and both values are dictionaries,
they will be merged recursively. Otherwise, the value from `src` overrides the one in `dest`.
This is useful for applying nested configuration overrides without losing existing structure.
Args:
dest (Dict[str, Any]): The original dictionary to merge into.
src (Dict[str, Any]): The dictionary containing overriding or additional values.
Returns:
Dict[str, Any]: A new dictionary representing the merged result.
Example:
deep_merge({"ptp4l": [{"name": "ptp4", "controller-1": {"grandmaster_settings": {"clock_class": 165}}}]}}
"""
result = copy.deepcopy(dest)
for key, value in src.items():
if isinstance(value, dict) and isinstance(result.get(key), dict):
result[key] = self.deep_merge(result[key], value)
else:
result[key] = value
return result