Added GNSS-Monitoring test cases

Change-Id: I0e3211b81c16b91c47e68aa5c573b7b6b3b065e4
Signed-off-by: Guntaka Umashankar Reddy <umashankarguntaka.reddy@windriver.com>
This commit is contained in:
Guntaka Umashankar Reddy
2025-11-11 11:52:57 -05:00
parent ee045460b5
commit 19a8dacc30
17 changed files with 1887 additions and 16 deletions

View File

@@ -0,0 +1,272 @@
from framework.logging.automation_logger import get_logger
from framework.validation.validation import validate_equals, validate_equals_with_retry, validate_str_contains
from keywords.base_keyword import BaseKeyword
from keywords.cloud_platform.command_wrappers import source_openrc
from keywords.cloud_platform.fault_management.alarms.alarm_list_keywords import AlarmListKeywords
from keywords.cloud_platform.fault_management.alarms.objects.alarm_list_object import AlarmListObject
from keywords.cloud_platform.system.ptp.objects.gnss_monitoring_data_output import GnssMonitoringDataOutput
from keywords.cloud_platform.system.ptp.system_ptp_instance_keywords import SystemPTPInstanceKeywords
from keywords.files.file_keywords import FileKeywords
from keywords.linux.systemctl.systemctl_status_keywords import SystemCTLStatusKeywords
from keywords.ptp.cat.cat_ptp_config_keywords import CatPtpConfigKeywords
from keywords.ptp.cat.gnss_monitor_conf_keywords import GnssMonitorConfKeywords
class GnssMonitoringKeywords(BaseKeyword):
"""
Keywords for GNSS monitoring operations using existing system keywords.
"""
def __init__(self, ssh_connection):
"""
Initialize GNSS monitoring keywords.
Args:
ssh_connection (SSHConnection): SSH connection to the target host.
"""
self.ssh_connection = ssh_connection
def get_gnss_monitoring_data(self, device_path: str) -> GnssMonitoringDataOutput:
"""
Get GNSS monitoring data using the CLI tool.
Args:
device_path (str): Path to the GNSS device (e.g., "/dev/ttyACM0").
Returns:
GnssMonitoringDataOutput: Parsed monitoring data output.
"""
get_logger().log_info(f"Getting GNSS monitoring data for device {device_path}")
cli_command = f"python /usr/rootdirs/opt/collectd/extensions/python/ptp_gnss_monitor_cli.py --devices {device_path}"
output = self.ssh_connection.send_as_sudo(cli_command)
self.validate_success_return_code(self.ssh_connection)
return GnssMonitoringDataOutput(output)
def create_monitoring_instance(self, instance_name: str) -> None:
"""
Create a GNSS monitoring PTP instance.
Args:
instance_name (str): Name of the monitoring instance.
Returns: None
"""
self.ptp_instance_keywords = SystemPTPInstanceKeywords(self.ssh_connection)
get_logger().log_info(f"Creating GNSS monitoring instance {instance_name}")
return self.ptp_instance_keywords.system_ptp_instance_add(instance_name, "gnss-monitor")
def verify_monitoring_configuration_file(self, expected_satellite_count: int, expected_signal_quality: int, expected_devices: str) -> None:
"""
Verify the monitoring configuration file contains expected values.
Args:
expected_satellite_count (int): Expected satellite count threshold.
expected_signal_quality (int): Expected signal quality threshold.
expected_devices (str): Expected device paths.
Returns: None
"""
get_logger().log_info("Verifying monitoring configuration file")
gnss_conf_output = GnssMonitorConfKeywords((self.ssh_connection)).cat_gnss_monitor_conf("/etc/linuxptp/ptpinstance/gnss-monitor-ptp.conf")
gnss_conf_object = gnss_conf_output.get_gnss_monitor_conf_object()
actual_devices = gnss_conf_object.get_devices()
actual_satellite_count = gnss_conf_object.get_satellite_count()
actual_signal_quality_db = gnss_conf_object.get_signal_quality_db()
validate_equals(actual_satellite_count, expected_satellite_count, "satellite_count not found in configuration")
validate_equals(actual_signal_quality_db, expected_signal_quality, "signal_quality_db not found in configuration")
validate_equals(actual_devices, expected_devices, "devices not found in configuration")
def wait_for_monitoring_configuration_file(self, expected_satellite_count: int, expected_signal_quality: int, expected_devices: str) -> None:
"""
Wait for monitoring configuration file to contain expected values.
Args:
expected_satellite_count (int): Expected satellite count threshold.
expected_signal_quality (int): Expected signal quality threshold.
expected_devices (str): Expected device paths.
Returns: None
Raises:
TimeoutError: raised when validate does not equal in the required time
"""
get_logger().log_info("Waiting for monitoring configuration file to match expected values")
def check_monitoring_config_matches() -> bool:
"""
Check if configuration file matches expected values.
Returns:
bool: True if all values match, False otherwise.
"""
gnss_conf_output = GnssMonitorConfKeywords(self.ssh_connection).cat_gnss_monitor_conf("/etc/linuxptp/ptpinstance/gnss-monitor-ptp.conf")
gnss_conf_object = gnss_conf_output.get_gnss_monitor_conf_object()
actual_devices = gnss_conf_object.get_devices()
actual_satellite_count = gnss_conf_object.get_satellite_count()
actual_signal_quality_db = gnss_conf_object.get_signal_quality_db()
return actual_satellite_count == expected_satellite_count and actual_signal_quality_db == expected_signal_quality and actual_devices == expected_devices
validate_equals_with_retry(check_monitoring_config_matches, True, "monitoring configuration file to match expected values", 120, 10)
def verify_gpsd_service_status(self, devices: list[str], expected_status: str = "active") -> None:
"""
Verify gpsd service status and gpspipe services for devices.
Args:
devices (list[str]): List of device paths (e.g., ["/dev/ttyACM0", "/dev/gnssx"]).
expected_status (str): Expected service status (default: "active").
Returns: None
"""
get_logger().log_info("Verifying gpsd service status")
service_status = SystemCTLStatusKeywords(self.ssh_connection).get_status("gpsd.service")
status_output = "\n".join(service_status) if isinstance(service_status, list) else service_status
validate_str_contains(status_output, f"Active: {expected_status}", "gpsd service status")
for device in devices:
device_name = device.replace("/dev/", "").replace("/", "-")
gpspipe_service = f"gpspipe@-dev-{device_name}.service"
validate_str_contains(status_output, gpspipe_service, f"gpspipe service for {device_name} should be running")
def verify_pty_device_exists(self, device_path: str, should_exist: bool = True) -> None:
"""
Verify PTY device exists or doesn't exist.
Args:
device_path (str): Path to the original device (e.g., "/dev/ttyACM0").
should_exist (bool): Whether the PTY device should exist (default: True).
Returns: None
"""
pty_path = f"{device_path}.pty"
get_logger().log_info(f"Verifying PTY device {pty_path} existence: {should_exist}")
file_keywords = FileKeywords(self.ssh_connection)
pty_exists = file_keywords.file_exists(pty_path)
validate_equals(pty_exists, should_exist, f"PTY device {pty_path} expected existance: {should_exist}")
def wait_for_monitoring_services_active(self, devices: list[str]) -> None:
"""
Wait for monitoring services to be active and running.
Args:
devices (list[str]): List of device paths (e.g., ["/dev/ttyACM0", "/dev/gnssx"]).
Returns: None
Raises:
TimeoutError: raised when validate does not equal in the required time
"""
get_logger().log_info("Waiting for monitoring services to be active")
def check_services_active() -> bool:
"""
Checks if gpsd and gpspipe services are active and running.
Returns:
bool: True if all services are active and running, False otherwise.
"""
gpsd_status_output = SystemCTLStatusKeywords(self.ssh_connection).get_status("gpsd")
if not any("Active: active (running)" in line for line in gpsd_status_output or []):
return False
for device in devices:
device_name = device.replace("/dev/", "").replace("/", "-")
gpspipe_service = f"gpspipe@-dev-{device_name}.service"
gpspipe_status_output = SystemCTLStatusKeywords(self.ssh_connection).get_status(gpspipe_service)
if not any("Active: active (running)" in line for line in gpspipe_status_output or []):
return False
return True
validate_equals_with_retry(check_services_active, True, "systemctl status for gpsd services to be active", 180, 30)
alarm_list_object = AlarmListObject()
alarm_list_object.set_alarm_id("250.001")
AlarmListKeywords(self.ssh_connection).set_timeout_in_seconds(180)
AlarmListKeywords(self.ssh_connection).wait_for_alarms_cleared([alarm_list_object])
def wait_for_monitoring_services_inactive(self) -> None:
"""
Wait for monitoring services to be inactive.
Returns: None
Raises:
TimeoutError: raised when validate does not equal in the required time
"""
get_logger().log_info("Waiting for monitoring services to be inactive")
def check_services_inactive() -> bool:
"""
Checks if gpsd service is inactive.
Returns:
bool: True if gpsd service is inactive, False otherwise.
"""
gpsd_status_output = SystemCTLStatusKeywords(self.ssh_connection).get_status("gpsd")
return any("Active: inactive (dead)" in line for line in gpsd_status_output or [])
validate_equals_with_retry(check_services_inactive, True, "systemctl status for gpsd services to be inactive", 120, 30)
alarm_objects = []
for alarm_id in ["250.001", "100.119"]:
alarm_obj = AlarmListObject()
alarm_obj.set_alarm_id(alarm_id)
alarm_objects.append(alarm_obj)
AlarmListKeywords(self.ssh_connection).wait_for_alarms_cleared(alarm_objects)
def verify_device_exists(self, device_path: str) -> None:
"""
Verify that a device exists on the system.
Args:
device_path (str): Path to the device (e.g., "/dev/ttyACM0").
Returns: None
"""
get_logger().log_info(f"Verifying device {device_path} exists")
device_check = self.ssh_connection.send(f"ls {device_path}")
device_check_str = "\n".join(device_check) if isinstance(device_check, list) else device_check
validate_str_contains(device_check_str, device_path, f"Device {device_path} should exist")
def verify_gnss_pty_data(self, device_path: str) -> None:
"""
Verify GNSS data through PTY device.
Args:
device_path (str): Path to the original device (e.g., "/dev/ttyACM0").
Returns: None
"""
pty_path = f"{device_path}.pty"
get_logger().log_info(f"Verifying GNSS data from PTY device {pty_path}")
gnss_data = self.ssh_connection.send_as_sudo(f"timeout 60 cat {pty_path} | head -20")
gnss_data_str = "\n".join(gnss_data) if isinstance(gnss_data, list) else gnss_data
validate_str_contains(gnss_data_str, "$GP", "Should receive NMEA data through PTY device")
def verify_ts2phc_config_serialport(self, instance_name: str, expected_serialport: str) -> None:
"""
Verify ts2phc configuration file contains correct nmea_serialport setting.
Args:
instance_name (str): Name of the PTP instance.
expected_serialport (str): Expected serialport path (e.g., "/dev/ttyACM0" or "/dev/ttyACM0.pty").
Returns: None
"""
config_file = f"/etc/linuxptp/ptpinstance/ts2phc-{instance_name}.conf"
get_logger().log_info(f"Verifying ts2phc config file {config_file}")
cat_ptp_config_keywords = CatPtpConfigKeywords(self.ssh_connection)
cat_ptp_config_output = cat_ptp_config_keywords.cat_ptp_config(config_file)
get_pmc_get_default_data_set_object = cat_ptp_config_output.data_set_output.get_pmc_get_default_data_set_object()
observed_serialport = get_pmc_get_default_data_set_object.get_ts2phc_nmea_serialport()
validate_equals(observed_serialport, expected_serialport, "verify ts2phc.nmea_serialport")

View File

@@ -0,0 +1,142 @@
class GnssMonitoringDataObject:
"""
Represents GNSS monitoring data for a single device.
"""
def __init__(self):
"""
Initializes a GnssMonitoringDataObject instance.
"""
self.device_path = None
self.gpsd_running = None
self.lock_state = None
self.satellite_count = None
self.signal_quality_min = None
self.signal_quality_max = None
self.signal_quality_avg = None
def set_device_path(self, device_path: str):
"""
Setter for device path.
Args:
device_path (str): Path to the GNSS device.
"""
self.device_path = device_path
def get_device_path(self) -> str:
"""
Getter for device path.
Returns:
str: Path to the GNSS device.
"""
return self.device_path
def set_gpsd_running(self, gpsd_running: int):
"""
Setter for gpsd running status.
Args:
gpsd_running (int): GPSD running status (1 for running, 0 for not running).
"""
self.gpsd_running = gpsd_running
def get_gpsd_running(self) -> int:
"""
Getter for gpsd running status.
Returns:
int: GPSD running status.
"""
return self.gpsd_running
def set_lock_state(self, lock_state: int):
"""
Setter for lock state.
Args:
lock_state (int): Lock state (1 for locked, 0 for not locked).
"""
self.lock_state = lock_state
def get_lock_state(self) -> int:
"""
Getter for lock state.
Returns:
int: Lock state.
"""
return self.lock_state
def set_satellite_count(self, satellite_count: int):
"""
Setter for satellite count.
Args:
satellite_count (int): Number of satellites.
"""
self.satellite_count = satellite_count
def get_satellite_count(self) -> int:
"""
Getter for satellite count.
Returns:
int: Number of satellites.
"""
return self.satellite_count
def set_signal_quality_min(self, signal_quality_min: float):
"""
Setter for minimum signal quality.
Args:
signal_quality_min (float): Minimum signal quality in dB-Hz.
"""
self.signal_quality_min = signal_quality_min
def get_signal_quality_min(self) -> float:
"""
Getter for minimum signal quality.
Returns:
float: Minimum signal quality.
"""
return self.signal_quality_min
def set_signal_quality_max(self, signal_quality_max: float):
"""
Setter for maximum signal quality.
Args:
signal_quality_max (float): Maximum signal quality in dB-Hz.
"""
self.signal_quality_max = signal_quality_max
def get_signal_quality_max(self) -> float:
"""
Getter for maximum signal quality.
Returns:
float: Maximum signal quality.
"""
return self.signal_quality_max
def set_signal_quality_avg(self, signal_quality_avg: float):
"""
Setter for average signal quality.
Args:
signal_quality_avg (float): Average signal quality in dB-Hz.
"""
self.signal_quality_avg = signal_quality_avg
def get_signal_quality_avg(self) -> float:
"""
Getter for average signal quality.
Returns:
float: Average signal quality.
"""
return self.signal_quality_avg

View File

@@ -0,0 +1,85 @@
import re
from typing import Union
from keywords.cloud_platform.system.ptp.objects.gnss_monitoring_data_object import GnssMonitoringDataObject
class GnssMonitoringDataOutput:
"""
This class parses GNSS monitoring CLI tool output and creates GnssMonitoringDataObject instances.
"""
def __init__(self, cli_output: Union[str, list[str]]):
"""
Initialize with CLI tool output.
Args:
cli_output (Union[str, list[str]]): Output from GNSS monitoring CLI tool.
"""
self.raw_output = cli_output
self.monitoring_data = self._parse_monitoring_data()
def _parse_monitoring_data(self) -> list[GnssMonitoringDataObject]:
"""
Parse the CLI output and create monitoring data objects.
Returns:
list[GnssMonitoringDataObject]: List of parsed monitoring data objects.
"""
monitoring_data = []
# Convert to string if it's a list
if isinstance(self.raw_output, list):
output_text = "\n".join(self.raw_output)
else:
output_text = self.raw_output
# Parse device data using regex
# Example: /dev/ttyACM0's gps_data: GpsData(gpsd_running=1, lock_state=1, satellite_count=21, signal_quality_db=SignalQualityDb(min=42.0, max=47.0, avg=44.571))
device_pattern = r"(/dev/\w+)'s gps_data: GpsData\(gpsd_running=(\d+), lock_state=(\d+), satellite_count=(\d+), signal_quality_db=SignalQualityDb\(min=([\d.]+), max=([\d.]+), avg=([\d.]+)\)\)"
matches = re.findall(device_pattern, output_text)
for match in matches:
device_path, gpsd_running, lock_state, satellite_count, sig_min, sig_max, sig_avg = match
data_obj = GnssMonitoringDataObject()
data_obj.set_device_path(device_path)
data_obj.set_gpsd_running(int(gpsd_running))
data_obj.set_lock_state(int(lock_state))
data_obj.set_satellite_count(int(satellite_count))
data_obj.set_signal_quality_min(float(sig_min))
data_obj.set_signal_quality_max(float(sig_max))
data_obj.set_signal_quality_avg(float(sig_avg))
monitoring_data.append(data_obj)
return monitoring_data
def get_monitoring_data(self) -> list[GnssMonitoringDataObject]:
"""
Get all monitoring data objects.
Returns:
list[GnssMonitoringDataObject]: List of monitoring data objects.
"""
return self.monitoring_data
def get_monitoring_data_for_device(self, device_path: str) -> GnssMonitoringDataObject:
"""
Get monitoring data for a specific device.
Args:
device_path (str): Path to the device.
Returns:
GnssMonitoringDataObject: Monitoring data for the specified device.
Raises:
ValueError: If device not found in monitoring data.
"""
for data in self.monitoring_data:
if data.get_device_path() == device_path:
return data
raise ValueError(f"Device '{device_path}' not found in monitoring data")

View File

@@ -55,4 +55,4 @@ class SystemPTPInstanceOutput:
Returns:
str: ptp instance parameters
"""
return PTPParametersParser(self.system_ptp_instance_object.get_parameters()).process_cmdline_opts()
return PTPParametersParser(self.system_ptp_instance_object.get_parameters()).process_parameters()

View File

@@ -47,3 +47,34 @@ class PTPParametersParser:
output_str = parameters_str
return output_str
def process_parameters(self) -> str:
"""
Processes all PTP parameters, handling both string and list inputs,
to ensure values are properly quoted.
Returns:
str: The modified string with parameter values properly quoted.
"""
if isinstance(self.parameters, list):
parameters_str = " ".join(self.parameters) # Convert list to string
else:
parameters_str = self.parameters
# Process devices parameter
devices_match = re.search(r"(devices=)(.*?)(?=\s+\w+=|$)", parameters_str)
if devices_match:
prefix, value = devices_match.group(1), devices_match.group(2).strip()
if " " in value and not (value.startswith("'") and value.endswith("'")):
value = f"'{value}'"
parameters_str = parameters_str.replace(devices_match.group(0), f"{prefix}{value}")
# Process cmdline_opts parameter
cmdline_match = re.search(r"(cmdline_opts=)(.*?)(?=\s+\w+=|$)", parameters_str)
if cmdline_match:
prefix, value = cmdline_match.group(1), cmdline_match.group(2).strip()
if not (value.startswith("'") and value.endswith("'")):
value = f"'{value}'"
parameters_str = parameters_str.replace(cmdline_match.group(0), f"{prefix}{value}")
return parameters_str

View File

@@ -228,22 +228,16 @@ class PTPSetupExecutorKeywords(BaseKeyword):
validate_equals_with_retry(check_ptp4l_status, True, "systemctl status for ptp4l", 120, 30)
# wait for SMA status
for clock_instance_obj in self.clock_setup_list:
ifaces_to_check = [(host, iface) for host in clock_instance_obj.get_instance_hostnames() for ptp_host_if in clock_instance_obj.get_ptp_interfaces() if "input" in ptp_host_if.get_ptp_interface_parameter() for iface in filter(None, ptp_host_if.get_interfaces_for_hostname(host))]
for host, interface in ifaces_to_check:
pci_address = gnss_keywords.get_pci_slot_name(host, interface)
cgu_location = f"/sys/kernel/debug/ice/{pci_address}/cgu"
gnss_keywords.validate_sma1_and_gnss_1pps_eec_pps_dpll_status_with_retry(host, cgu_location, "SMA1", timeout=180, polling_interval=30)
# wait for GNSS status
for ts2phc_instance_obj in self.ts2phc_setup_list:
expected_gnss_port = gnss_keywords.extract_gnss_port(ts2phc_instance_obj.get_instance_parameters())
if not expected_gnss_port:
continue
if expected_gnss_port == "ttyACM0":
get_logger().log_info(f"Skipping CGU debug validation for {expected_gnss_port} - not valid for USB serial device type")
return
ifaces_to_check = []
for host in ts2phc_instance_obj.get_instance_hostnames():
for ptp_host_if in ts2phc_instance_obj.get_ptp_interfaces():
@@ -257,3 +251,13 @@ class PTPSetupExecutorKeywords(BaseKeyword):
pci_address = gnss_keywords.get_pci_slot_name(host, interface)
cgu_location = f"/sys/kernel/debug/ice/{pci_address}/cgu"
gnss_keywords.validate_sma1_and_gnss_1pps_eec_pps_dpll_status_with_retry(host, cgu_location, timeout=180, polling_interval=30)
# wait for SMA status
for clock_instance_obj in self.clock_setup_list:
ifaces_to_check = [(host, iface) for host in clock_instance_obj.get_instance_hostnames() for ptp_host_if in clock_instance_obj.get_ptp_interfaces() if "input" in ptp_host_if.get_ptp_interface_parameter() for iface in filter(None, ptp_host_if.get_interfaces_for_hostname(host))]
for host, interface in ifaces_to_check:
pci_address = gnss_keywords.get_pci_slot_name(host, interface)
cgu_location = f"/sys/kernel/debug/ice/{pci_address}/cgu"
gnss_keywords.validate_sma1_and_gnss_1pps_eec_pps_dpll_status_with_retry(host, cgu_location, "SMA1", timeout=210, polling_interval=60)

View File

@@ -81,6 +81,10 @@ class PTPVerifyConfigKeywords(BaseKeyword):
get_logger().log_info("Validation skipped as expected; GNSS port is None")
continue
if expected_gnss_port == "ttyACM0":
get_logger().log_info(f"Skipping CGU debug validation for {expected_gnss_port} - not valid for USB serial device type")
return
for ptp_host_if in ts2phc_instance_obj.get_ptp_interfaces():
for host in hosts:
interfaces = ptp_host_if.get_interfaces_for_hostname(host.get_host_name())

View File

@@ -0,0 +1,27 @@
from framework.ssh.ssh_connection import SSHConnection
from keywords.base_keyword import BaseKeyword
from keywords.ptp.cat.objects.gnss_monitor_conf_output import GnssMonitorConfOutput
class GnssMonitorConfKeywords(BaseKeyword):
"""
Class for GNSS Monitor Conf Keywords.
"""
def __init__(self, ssh_connection: SSHConnection):
self.ssh_connection = ssh_connection
def cat_gnss_monitor_conf(self, gnss_monitor_conf_location: str) -> GnssMonitorConfOutput:
"""
Runs the command sudo cat <gnss_monitor_conf_location> ex. /etc/linuxptp/ptpinstance/gnss-monitor-ptp.conf.
Args:
gnss_monitor_conf_location (str): the GNSS monitor conf location.
Returns:
GnssMonitorConfOutput: the GnssMonitorConfOutput.
"""
output = self.ssh_connection.send_as_sudo(f"cat {gnss_monitor_conf_location}")
gnss_monitor_conf_output = GnssMonitorConfOutput(output)
return gnss_monitor_conf_output

View File

@@ -0,0 +1,59 @@
from framework.exceptions.keyword_exception import KeywordException
class GnssMonitorConfParser:
"""
Class for GNSS monitor conf parsing
Example:
[global]
##
## Default Data Set
##
devices /dev/ttyACM0 /dev/gnssx
satellite_count 8
signal_quality_db 30
"""
def __init__(self, gnss_monitor_conf_output: list[str]):
"""
Constructor
Args:
gnss_monitor_conf_output (list[str]): a list of strings representing the output of a 'cat gnss-monitor-ptp.conf' command.
"""
self.gnss_monitor_conf_output = gnss_monitor_conf_output
def get_output_values_dict(self) -> dict:
"""
Getter for output values dict
Returns:
dict: the output values dict
"""
output_values_dict = {}
for row in self.gnss_monitor_conf_output:
if "~$" in row or "Password:" in row:
continue # these prompts should be ignored
# Skip empty lines, comments, and section headers
stripped_row = row.strip()
if not stripped_row or stripped_row.startswith("#") or stripped_row.startswith("["):
continue
# Split on first space to handle values with spaces
parts = stripped_row.split(None, 1)
if len(parts) == 2:
key, value = parts
output_values_dict[key.strip()] = value.strip()
elif len(parts) == 1:
# Handle cases where there might be a key without value
key = parts[0].strip()
if key:
output_values_dict[key] = ""
else:
raise KeywordException(f"Line with values: {row} was not in the expected format")
return output_values_dict

View File

@@ -0,0 +1,69 @@
class GnssMonitorConfObject:
"""
Object to hold the values of GNSS Monitor conf Object
"""
def __init__(self):
self.devices: str = ""
self.satellite_count: int = 0
self.signal_quality_db: int = 0
def set_devices(self, devices: str):
"""
Setter for devices
Args:
devices (str): the devices
"""
self.devices = devices
def get_devices(self) -> str:
"""
Getter for devices
Returns:
str: the devices
"""
return self.devices
def set_satellite_count(self, satellite_count: int):
"""
Setter for satellite_count
Args:
satellite_count (int): the satellite_count
"""
self.satellite_count = satellite_count
def get_satellite_count(self) -> int:
"""
Getter for satellite_count
Returns:
int: the satellite_count
"""
return self.satellite_count
def set_signal_quality_db(self, signal_quality_db: int):
"""
Setter for signal_quality_db
Args:
signal_quality_db (int): the signal_quality_db
"""
self.signal_quality_db = signal_quality_db
def get_signal_quality_db(self) -> int:
"""
Getter for signal_quality_db
Returns:
int: the signal_quality_db
"""
return self.signal_quality_db

View File

@@ -0,0 +1,50 @@
from keywords.ptp.cat.gnss_monitor_conf_parser import GnssMonitorConfParser
from keywords.ptp.cat.objects.gnss_monitor_conf_object import GnssMonitorConfObject
class GnssMonitorConfOutput:
"""
This class parses the output of cat GNSS monitor conf file
Example:
[global]
##
## Default Data Set
##
devices /dev/ttyACM0 /dev/gnssx
satellite_count 8
signal_quality_db 30
"""
def __init__(self, gnss_monitor_conf_output: list[str]):
"""
Create an internal GnssMonitorConfObject from the passed parameter.
Args:
gnss_monitor_conf_output (list[str]): a list of strings representing the GNSS monitor conf output
"""
gnss_monitor_conf_parser = GnssMonitorConfParser(gnss_monitor_conf_output)
output_values = gnss_monitor_conf_parser.get_output_values_dict()
self.gnss_monitor_conf_object = GnssMonitorConfObject()
if "devices" in output_values:
self.gnss_monitor_conf_object.set_devices(output_values["devices"])
if "satellite_count" in output_values:
self.gnss_monitor_conf_object.set_satellite_count(int(output_values["satellite_count"]))
if "signal_quality_db" in output_values:
self.gnss_monitor_conf_object.set_signal_quality_db(int(output_values["signal_quality_db"]))
def get_gnss_monitor_conf_object(self) -> GnssMonitorConfObject:
"""
Getter for GnssMonitorConfObject.
Returns:
GnssMonitorConfObject: The GnssMonitorConfObject
"""
return self.gnss_monitor_conf_object

View File

@@ -76,9 +76,18 @@ class PTPServiceStatusValidator(BaseKeyword):
observed_service_status = service_status_output.get_ptp4l_object(name).get_active()
get_command = service_status_output.get_ptp4l_object(name).get_command()
# From the input string "cmdline_opts='-s enpXXs0f2 -O -37 -m'"
# The extracted output string is '-s enpXXs0f2 -O -37 -m'
instance_parameter = eval(instance_parameters.split("=")[1])
# Extract cmdline_opts parameter from the instance_parameters string
# Handle cases like "cmdline_opts='-w -s eno1' domainNumber=24 uds_address=/var/run/ptp4l-ptp-inst1"
cmdline_match = re.search(r"cmdline_opts='([^']+)'", instance_parameters)
if cmdline_match:
instance_parameter = cmdline_match.group(1)
else:
# Fallback for simple cases without quotes
parts = instance_parameters.split("=", 1)
if len(parts) > 1:
instance_parameter = parts[1].split()[0] # Take first part before space
else:
instance_parameter = ""
if expected_service_status in observed_service_status and instance_parameter in get_command:
get_logger().log_info(f"Validation Successful - systemctl status {service_name}")

View File

@@ -1,5 +1,7 @@
from typing import Any, Dict, List
from config.configuration_manager import ConfigurationManager
class PTPHostInterfaceSetup:
"""
@@ -19,6 +21,8 @@ class PTPHostInterfaceSetup:
Exception: If the setup_dict does not contain required controller interfaces.
Exception: If the setup_dict does not contain required compute interfaces.
"""
lab_type = ConfigurationManager.get_lab_config().get_lab_type()
if "name" not in setup_dict:
raise Exception("Every ptp host interface entry should have a name.")
self.name = setup_dict["name"]
@@ -31,9 +35,9 @@ class PTPHostInterfaceSetup:
raise Exception(f"The ptp host interface entry {self.name} must have controller_0_interfaces defined.")
self.controller_0_interfaces = setup_dict["controller_0_interfaces"]
if "controller_1_interfaces" not in setup_dict:
if "controller_1_interfaces" not in setup_dict and lab_type != "Simplex":
raise Exception(f"The ptp host interface entry {self.name} must have controller_1_interfaces defined.")
self.controller_1_interfaces = setup_dict["controller_1_interfaces"]
self.controller_1_interfaces = setup_dict.get("controller_1_interfaces")
self.compute_0_interfaces = None
if "compute_0_interfaces" in setup_dict:

View File

@@ -0,0 +1,102 @@
{
// PTP Configuration Expectation for GNR-D GNSS
// This file defines the expected PTP configuration for AIO-SX system
// Based on the provided system commands for PTP setup
// Service types:
// - T-TSC: Telecom Time Slave Clock - Synchronizes to an upstream master clock via GNSS
// Time sources:
// - GNSS: Global Navigation Satellite System provides time via /dev/ttyACM0
ptp_instances: {
ptp4l: [
{
// PTP-INST1 instance - Telecom Time Slave Clock (T-TSC)
// Receives time from GNSS via ts2phc and distributes via eno1
// Domain 24 with G.8275.x profile
name: "ptp-inst1",
instance_hostnames : ["controller-0"],
instance_parameters: "domainNumber=24 tx_timestamp_timeout=700 dataset_comparison=G.8275.x fault_reset_interval=0 logAnnounceInterval=-3 logMinDelayReqInterval=-4 logSyncInterval=-4",
ptp_interface_names: [
"ptp-iface1",
],
},
],
// phc2sys - Synchronizes system clock to PTP hardware clock
// Uses eno1 interface with specific command line options
phc2sys : [
{
// PHC-INST1 instance - Synchronizes controller-0 system clock
// Uses eno1 interface with wait mode and specific UDS address
name: "phc-inst1",
instance_hostnames : ["controller-0"],
instance_parameters: "cmdline_opts='-w -s {{ controller_0.nic1.nic_connection.interface }}' domainNumber=24 uds_address=/var/run/ptp4l-ptp-inst1",
ptp_interface_names: [],
},
],
// ts2phc - Synchronizes PTP hardware clock to external time source
// Uses GNSS receiver via NMEA serial port /dev/ttyACM0
ts2phc : [
{
// TS1 instance - Synchronizes controller-0 to GNSS time source
// Uses NMEA serial port /dev/ttyACM0 for GNSS connection
name: "ts1",
instance_hostnames : ["controller-0"],
instance_parameters: "ts2phc.nmea_serialport=/dev/ttyACM0",
ptp_interface_names: [
"tsint1",
],
},
],
},
// PTP Host Interfaces - Maps logical PTP interfaces to physical network interfaces
// Defines which physical interfaces are used for each PTP instance on each host
ptp_host_ifs: [
{
name: "ptp-iface1",
controller_0_interfaces: ["{{ controller_0.nic1.nic_connection.interface }}"],
ptp_interface_parameter : "",
},
{
name: "tsint1",
controller_0_interfaces: ["{{ controller_0.nic1.nic_connection.interface }}"],
ptp_interface_parameter : "",
},
],
// This section is for validation purposes. All expected values are maintained here
// Used by test framework to verify correct PTP configuration and behavior
// Defines expected port states, parent-child relationships, and clock settings
"expected_dict": {
"ptp4l": [
{
"name": "ptp-inst1",
"controller-0": {
"parent_data_set" : {
"gm_clock_class": [6, 7],
"gm_clock_accuracy": "0x20",
"gm_offset_scaled_log_variance": "0x4e5d"
},
"time_properties_data_set": {
"current_utc_offset": 37,
"current_utc_offset_valid": 0,
"time_traceable": 1,
"frequency_traceable": 1
},
"grandmaster_settings": {{ grandmaster_settings.grandmaster_settings_tgm_default }},
"port_data_set": [
{
"interface" : "{{ controller_0.nic1.nic_connection.interface }}",
"port_state": "MASTER"
}
]
}
}
]
}
}

View File

@@ -0,0 +1,947 @@
from pytest import mark
from framework.logging.automation_logger import get_logger
from framework.resources.resource_finder import get_stx_resource_path
from framework.validation.validation import validate_equals, validate_str_contains
from keywords.cloud_platform.fault_management.alarms.alarm_list_keywords import AlarmListKeywords
from keywords.cloud_platform.fault_management.alarms.objects.alarm_list_object import AlarmListObject
from keywords.cloud_platform.ssh.lab_connection_keywords import LabConnectionKeywords
from keywords.cloud_platform.system.host.system_host_list_keywords import SystemHostListKeywords
from keywords.cloud_platform.system.host.system_host_lock_keywords import SystemHostLockKeywords
from keywords.cloud_platform.system.host.system_host_reboot_keywords import SystemHostRebootKeywords
from keywords.cloud_platform.system.ptp.gnss_monitoring_keywords import GnssMonitoringKeywords
from keywords.cloud_platform.system.ptp.ptp_readiness_keywords import PTPReadinessKeywords
from keywords.cloud_platform.system.ptp.ptp_setup_executor_keywords import PTPSetupExecutorKeywords
from keywords.cloud_platform.system.ptp.ptp_teardown_executor_keywords import PTPTeardownExecutorKeywords
from keywords.cloud_platform.system.ptp.ptp_verify_config_keywords import PTPVerifyConfigKeywords
from keywords.cloud_platform.system.ptp.system_host_ptp_instance_keywords import SystemHostPTPInstanceKeywords
from keywords.cloud_platform.system.ptp.system_ptp_instance_keywords import SystemPTPInstanceKeywords
from keywords.cloud_platform.system.ptp.system_ptp_instance_parameter_keywords import SystemPTPInstanceParameterKeywords
from keywords.files.file_keywords import FileKeywords
from keywords.ptp.setup.ptp_setup_reader import PTPSetupKeywords
@mark.p0
@mark.lab_has_gnr_d
def test_delete_and_add_all_ptp_configuration() -> None:
"""This test verifies that all PTP configurations can be cleanly removed
and re-added, ensuring proper system state management.
Test Steps:
1. Delete all existing PTP configurations
2. Add all PTP configurations from template
3. Verify all PTP configurations are properly applied
"""
lab_connect_keywords = LabConnectionKeywords()
ssh_connection = lab_connect_keywords.get_active_controller_ssh()
get_logger().log_info("Delete all PTP configuration")
ptp_teardown_keywords = PTPTeardownExecutorKeywords(ssh_connection)
ptp_teardown_keywords.delete_all_ptp_configurations()
get_logger().log_info("Add all PTP configuration")
ptp_setup_template_path = get_stx_resource_path("resources/ptp/setup/ptp_configuration_expectation_gnr_d.json5")
ptp_setup_executor_keywords = PTPSetupExecutorKeywords(ssh_connection, ptp_setup_template_path)
ptp_setup_executor_keywords.add_all_ptp_configurations()
ptp_readiness_keywords = PTPReadinessKeywords(LabConnectionKeywords().get_ssh_for_hostname("controller-0"))
ptp_readiness_keywords.wait_for_port_state_appear_in_port_data_set("ptp-inst1", ["MASTER"])
ptp_readiness_keywords.wait_for_gm_clock_class_appear_in_parent_data_set("ptp-inst1", [6, 7])
get_logger().log_info("Verify all PTP configuration")
ptp_setup_keywords = PTPSetupKeywords()
ptp_setup = ptp_setup_keywords.generate_ptp_setup_from_template(ptp_setup_template_path)
ptp_verify_config_keywords = PTPVerifyConfigKeywords(ssh_connection, ptp_setup)
ptp_verify_config_keywords.verify_all_ptp_configurations()
@mark.p0
@mark.lab_has_gnr_d
def test_gnss_environment_setup_verification(request) -> None:
"""Verify test environment setup and GNSS device availability for monitoring functionality.
This test ensures that all required GNSS hardware and software components
are properly configured and operational before running monitoring tests.
Args:
request (pytest.FixtureRequest): Pytest request fixture for test cleanup registration.
Test Steps:
1. Check GNSS device availability at /dev/ttyACM0
2. Verify GNSS data stream contains NMEA sentences
3. Check zl3073x kernel module is loaded
4. Verify network interface is UP with IP address
"""
lab_connect_keywords = LabConnectionKeywords()
ssh_connection = lab_connect_keywords.get_active_controller_ssh()
get_logger().log_test_case_step("Checking GNSS device availability")
gnss_monitoring_keywords = GnssMonitoringKeywords(ssh_connection)
gnss_monitoring_keywords.verify_device_exists("/dev/ttyACM0")
get_logger().log_test_case_step("Verifying GNSS data stream")
gnss_data = ssh_connection.send_as_sudo("timeout 5 cat /dev/ttyACM0 | head -10")
validate_str_contains(gnss_data, "$GN", "Should receive NMEA sentences")
get_logger().log_test_case_step("Checking zl3073x module")
module_check = ssh_connection.send("lsmod | grep zl3073x")
validate_str_contains(module_check, "zl3073x", "zl3073x module should be loaded")
get_logger().log_test_case_step("Verifying network interface")
ptp_setup_template_path = get_stx_resource_path("resources/ptp/setup/ptp_configuration_expectation_gnr_d.json5")
ptp_setup_keywords = PTPSetupKeywords()
ptp_setup = ptp_setup_keywords.generate_ptp_setup_from_template(ptp_setup_template_path)
interfaces = ptp_setup.get_ptp4l_setup("ptp1").get_ptp_interface("ptp1if1").get_interfaces_for_hostname("controller-0")
if not interfaces:
raise Exception("No interfaces found for controller-0 NIC1")
ctrl0_nic1_interface = interfaces[0]
interface_check = ssh_connection.send(f"ip a sh {ctrl0_nic1_interface}")
validate_str_contains(interface_check, "UP", "Interface should be UP")
validate_str_contains(interface_check, "inet", "Interface should have IP address")
@mark.p0
@mark.lab_has_gnr_d
def test_gnss_monitoring_basic_instance_creation(request) -> None:
"""Verify GNSS monitoring instance can be created and configured with proper service activation.
This test validates the complete lifecycle of creating a GNSS monitoring
instance, configuring parameters, and verifying proper operation including
service activation and alarm generation.
Args:
request (pytest.FixtureRequest): Pytest request fixture for test cleanup registration.
Test Steps:
1. Create and configure GNSS monitoring instance
2. Apply configuration and verify services
3. Verify PTY devices and data flow
4. Check metrics and alarm conditions
"""
lab_connect_keywords = LabConnectionKeywords()
ssh_connection = lab_connect_keywords.get_active_controller_ssh()
ptp_instance_keywords = SystemPTPInstanceKeywords(ssh_connection)
gnss_monitoring_keywords = GnssMonitoringKeywords(ssh_connection)
ptp_parameter_keywords = SystemPTPInstanceParameterKeywords(ssh_connection)
host_ptp_instance_keywords = SystemHostPTPInstanceKeywords(ssh_connection)
alarm_keywords = AlarmListKeywords(ssh_connection)
def cleanup_monitoring_instance():
"""Clean up GNSS monitoring instance."""
get_logger().log_teardown_step("Cleaning up GNSS monitoring instance")
host_ptp_instance_keywords.system_host_ptp_instance_remove_with_error("controller-0", "test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", "satellite_count=150")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", "signal_quality_db=300")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'devices="/dev/ttyACM0 /dev/gnssx"')
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'cmdline_opts="-D 7"')
ptp_instance_keywords.system_ptp_instance_delete_with_error("test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_inactive()
request.addfinalizer(cleanup_monitoring_instance)
get_logger().log_test_case_step("Creating GNSS monitor instance")
gnss_monitoring_keywords.create_monitoring_instance("test-monitor")
get_logger().log_test_case_step("Configuring monitoring parameters")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "satellite_count=150")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "signal_quality_db=300")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", 'devices="/dev/ttyACM0 /dev/gnssx"')
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", 'cmdline_opts="-D 7"')
get_logger().log_test_case_step("Assigning to host")
host_ptp_instance_keywords.system_host_ptp_instance_assign("controller-0", "test-monitor")
get_logger().log_test_case_step("Applying configuration")
ptp_instance_keywords.system_ptp_instance_apply()
get_logger().log_test_case_step("Waiting for services to be ready")
gnss_monitoring_keywords.wait_for_monitoring_services_active(devices=["/dev/ttyACM0", "/dev/gnssx"])
get_logger().log_test_case_step("Verifying GNSS monitor configuration file")
gnss_monitoring_keywords.verify_monitoring_configuration_file(150, 300, "/dev/ttyACM0 /dev/gnssx")
get_logger().log_test_case_step("Verify ts2phc configuration for nmea_serialport")
gnss_monitoring_keywords.verify_ts2phc_config_serialport("ts1", "/dev/ttyACM0.pty")
get_logger().log_test_case_step("Verifying PTY devices created")
gnss_monitoring_keywords.verify_pty_device_exists("/dev/ttyACM0", True)
gnss_monitoring_keywords.verify_device_exists("/dev/ttyACM0.pty")
get_logger().log_test_case_step("Testing GNSS data through PTY")
gnss_monitoring_keywords.verify_gnss_pty_data("/dev/ttyACM0")
get_logger().log_test_case_step("Checking current satellite count and signal quality for /dev/ttyACM0")
get_gnss_monitoring_data = gnss_monitoring_keywords.get_gnss_monitoring_data("/dev/ttyACM0")
get_satellite_count_ttyACM0 = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_satellite_count()
get_signal_quality_max_ttyACM0 = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_signal_quality_max()
get_logger().log_info(f"Current GNSS satellite count for /dev/ttyACM0: {get_satellite_count_ttyACM0}")
get_logger().log_info(f"Current GNSS signal quality for /dev/ttyACM0: {get_signal_quality_max_ttyACM0}")
get_logger().log_test_case_step("Checking current satellite count and signal quality for /dev/gnssx")
get_gnss_monitoring_data = gnss_monitoring_keywords.get_gnss_monitoring_data("/dev/gnssx")
get_satellite_count_gnssx = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/gnssx").get_satellite_count()
get_signal_quality_max_gnssx = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/gnssx").get_signal_quality_max()
get_logger().log_info(f"Current GNSS satellite count for /dev/gnssx: {get_satellite_count_gnssx}")
get_logger().log_info(f"Current GNSS signal quality for /dev/gnssx: {get_signal_quality_max_gnssx}")
get_logger().log_test_case_step("Verifying alarm conditions")
expected_alarms = []
# Check conditions and add expected alarms with dynamic values
get_logger().log_info(f"gnssx satellite count {get_satellite_count_gnssx} < 150, expecting satellite alarm")
satellite_alarm_gnssx = AlarmListObject()
satellite_alarm_gnssx.set_alarm_id("100.119")
satellite_alarm_gnssx.set_reason_text(f"controller-0 GNSS satellite count below threshold state: satellite count {get_satellite_count_gnssx} \(expected: >= 150\)")
satellite_alarm_gnssx.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/gnssx.ptp=GNSS-satellite-count")
expected_alarms.append(satellite_alarm_gnssx)
get_logger().log_info(f"gnssx signal quality {get_signal_quality_max_gnssx} < 300, expecting signal quality alarm")
signal_quality_alarm_gnssx = AlarmListObject()
signal_quality_alarm_gnssx.set_alarm_id("100.119")
signal_quality_alarm_gnssx.set_reason_text(r"controller-0 GNSS signal quality db below threshold state: signal_quality_db [\d\.]+\s+\(expected: >= 300.0\)")
signal_quality_alarm_gnssx.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/gnssx.ptp=GNSS-signal-quality-db")
expected_alarms.append(signal_quality_alarm_gnssx)
get_logger().log_info(f"ttyACM0 satellite count {get_satellite_count_ttyACM0} < 150, expecting satellite alarm")
satellite_alarm_ttyACM0 = AlarmListObject()
satellite_alarm_ttyACM0.set_alarm_id("100.119")
satellite_alarm_ttyACM0.set_reason_text(f"controller-0 GNSS satellite count below threshold state: satellite count {get_satellite_count_ttyACM0} \(expected: >= 150\)")
satellite_alarm_ttyACM0.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/ttyACM0.ptp=GNSS-satellite-count")
expected_alarms.append(satellite_alarm_ttyACM0)
get_logger().log_info(f"ttyACM0 signal quality {get_signal_quality_max_ttyACM0} < 300, expecting signal quality alarm")
signal_quality_alarm_ttyACM0 = AlarmListObject()
signal_quality_alarm_ttyACM0.set_alarm_id("100.119")
signal_quality_alarm_ttyACM0.set_reason_text(r"controller-0 GNSS signal quality db below threshold state: signal_quality_db [\d\.]+\s+\(expected: >= 300.0\)")
signal_quality_alarm_ttyACM0.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/ttyACM0.ptp=GNSS-signal-quality-db")
expected_alarms.append(signal_quality_alarm_ttyACM0)
# Always expect signal loss alarm for gnssx
signal_loss_alarm = AlarmListObject()
signal_loss_alarm.set_alarm_id("100.119")
signal_loss_alarm.set_reason_text("controller-0 GNSS signal loss state: signal lock False \(expected: True\)")
signal_loss_alarm.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/gnssx.ptp=GNSS-signal-loss")
expected_alarms.append(signal_loss_alarm)
get_logger().log_info(f"Expecting {len(expected_alarms)} alarms based on current conditions")
# Wait for expected alarms to appear
alarm_keywords.set_timeout_in_seconds(180)
alarm_keywords.wait_for_alarms_to_appear(expected_alarms)
get_logger().log_info("All expected alarms appeared successfully")
get_logger().log_test_case_step("Validate that only expected alarms are present")
all_alarms = alarm_keywords.alarm_list()
gnss_alarms = [alarm for alarm in all_alarms if "100.119" in alarm.get_alarm_id()]
validate_equals(len(gnss_alarms), len(expected_alarms), "Number of GNSS alarms should match expected count")
@mark.p0
@mark.lab_has_gnr_d
def test_gnss_monitoring_satellite_count_alarm(request) -> None:
"""Test GNSS monitoring satellite count alarm generation and clearing.
This test verifies that satellite count alarms are properly raised when
the threshold is set above current satellite count and cleared when
the threshold is lowered below current count.
Args:
request (pytest.FixtureRequest): Pytest request fixture for test cleanup.
Test Steps:
1. Create monitoring instance with high satellite threshold
2. Wait for satellite count alarm to appear
3. Lower threshold and verify alarm clears
"""
lab_connect_keywords = LabConnectionKeywords()
ssh_connection = lab_connect_keywords.get_active_controller_ssh()
ptp_instance_keywords = SystemPTPInstanceKeywords(ssh_connection)
ptp_parameter_keywords = SystemPTPInstanceParameterKeywords(ssh_connection)
host_ptp_instance_keywords = SystemHostPTPInstanceKeywords(ssh_connection)
gnss_monitoring_keywords = GnssMonitoringKeywords(ssh_connection)
get_logger().log_setup_step("Creating monitoring instance with high satellite threshold")
gnss_monitoring_keywords.create_monitoring_instance("test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "satellite_count=15")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", 'devices="/dev/ttyACM0"')
host_ptp_instance_keywords.system_host_ptp_instance_assign("controller-0", "test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0"])
get_logger().log_setup_step("Get current satellite count for /dev/ttyACM0 device")
get_gnss_monitoring_data = gnss_monitoring_keywords.get_gnss_monitoring_data("/dev/ttyACM0")
get_satellite_count = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_satellite_count()
get_logger().log_info(f"Current GNSS satellite count for /dev/ttyACM0 device: {get_satellite_count}")
# Increasing satellite count to trigger alarm - ensure it's always higher than current
if get_satellite_count > 15:
satellite_count = get_satellite_count + 15
ptp_parameter_keywords.system_ptp_instance_parameter_delete("test-monitor", "satellite_count=15")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", f"satellite_count={satellite_count}")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0"])
else:
satellite_count = get_satellite_count
# Calculate lowering satellite threshold to clear alarm - must be less than current count
lowering_satellite_count = max(0, get_satellite_count // 2)
get_logger().log_info(f"Will lower satellite threshold to {lowering_satellite_count} to clear alarm")
def cleanup_monitoring_instance():
"""Clean up GNSS monitoring instance."""
get_logger().log_teardown_step("Cleaning up GNSS monitoring instance")
host_ptp_instance_keywords.system_host_ptp_instance_remove_with_error("controller-0", "test-monitor")
if lowering_satellite_count is not None:
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", f"satellite_count={lowering_satellite_count}")
else:
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", f"satellite_count={satellite_count}")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'devices="/dev/ttyACM0"')
ptp_instance_keywords.system_ptp_instance_delete_with_error("test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_inactive()
request.addfinalizer(cleanup_monitoring_instance)
get_logger().log_test_case_step("Waiting for satellite count alarm to appear")
satellite_alarm = AlarmListObject()
satellite_alarm.set_alarm_id("100.119")
satellite_alarm.set_reason_text(f"controller-0 GNSS satellite count below threshold state: satellite count {get_satellite_count} \(expected: >= {satellite_count}\)")
satellite_alarm.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/ttyACM0.ptp=GNSS-satellite-count")
alarm_keywords = AlarmListKeywords(ssh_connection)
alarm_keywords.set_timeout_in_seconds(240)
alarm_keywords.wait_for_alarms_to_appear([satellite_alarm])
get_logger().log_test_case_step("Lowering satellite threshold to clear alarm")
ptp_parameter_keywords.system_ptp_instance_parameter_delete("test-monitor", f"satellite_count={satellite_count}")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", f"satellite_count={lowering_satellite_count}")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0"])
get_logger().log_test_case_step("Waiting for alarm to clear")
lowering_satellite_alarm = AlarmListObject()
lowering_satellite_alarm.set_alarm_id("100.119")
lowering_satellite_alarm.set_reason_text(f"controller-0 GNSS satellite count below threshold state: satellite count {get_satellite_count} \(expected: >= {lowering_satellite_count}\)")
lowering_satellite_alarm.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/ttyACM0.ptp=GNSS-satellite-count")
alarm_keywords.set_timeout_in_seconds(240)
alarm_keywords.wait_for_alarms_cleared([satellite_alarm, lowering_satellite_alarm])
@mark.p0
@mark.lab_has_gnr_d
def test_gnss_monitoring_signal_quality_alarm(request) -> None:
"""Test GNSS monitoring signal quality alarm generation and clearing.
This test verifies that signal quality alarms are properly raised when
the threshold is set above current signal quality and cleared when
the threshold is lowered below current quality.
Args:
request (pytest.FixtureRequest): Pytest request fixture for test cleanup registration.
Test Steps:
1. Create monitoring instance with high signal quality threshold
2. Wait for signal quality alarm to appear
3. Lower threshold and verify alarm clears
"""
lab_connect_keywords = LabConnectionKeywords()
ssh_connection = lab_connect_keywords.get_active_controller_ssh()
ptp_instance_keywords = SystemPTPInstanceKeywords(ssh_connection)
ptp_parameter_keywords = SystemPTPInstanceParameterKeywords(ssh_connection)
host_ptp_instance_keywords = SystemHostPTPInstanceKeywords(ssh_connection)
gnss_monitoring_keywords = GnssMonitoringKeywords(ssh_connection)
get_logger().log_setup_step("Creating monitoring instance with high signal quality threshold")
gnss_monitoring_keywords.create_monitoring_instance("test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "satellite_count=8")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", 'devices="/dev/ttyACM0"')
host_ptp_instance_keywords.system_host_ptp_instance_assign("controller-0", "test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0"])
get_logger().log_setup_step("Get current GNSS signal quality for /dev/ttyACM0 device")
get_gnss_monitoring_data = gnss_monitoring_keywords.get_gnss_monitoring_data("/dev/ttyACM0")
get_signal_quality_max = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_signal_quality_max()
get_logger().log_info(f"Current GNSS signal quality for /dev/ttyACM0 device: {get_signal_quality_max}")
# Increasing signal quality db to trigger alarm - ensure it's always higher than current
if get_signal_quality_max > 30:
signal_quality_db = get_signal_quality_max + 30
ptp_parameter_keywords.system_ptp_instance_parameter_delete("test-monitor", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", f"signal_quality_db={signal_quality_db}")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0"])
else:
signal_quality_db = get_signal_quality_max
# Calculate lowering signal quality db threshold to clear alarm - must be less than current count
lowering_signal_quality_db = max(0, get_signal_quality_max // 2)
get_logger().log_info(f"Will lower signal quality db threshold to {lowering_signal_quality_db} to clear alarm")
def cleanup_monitoring_instance():
"""Clean up GNSS monitoring instance."""
get_logger().log_teardown_step("Cleaning up GNSS monitoring instance")
host_ptp_instance_keywords.system_host_ptp_instance_remove_with_error("controller-0", "test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", "satellite_count=8")
if lowering_signal_quality_db is not None:
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", f"signal_quality_db={lowering_signal_quality_db}")
else:
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", f"signal_quality_db={signal_quality_db}")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'devices="/dev/ttyACM0"')
ptp_instance_keywords.system_ptp_instance_delete_with_error("test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_inactive()
request.addfinalizer(cleanup_monitoring_instance)
get_logger().log_test_case_step("Waiting for signal quality alarm to appear")
signal_quality_alarm = AlarmListObject()
signal_quality_alarm.set_alarm_id("100.119")
signal_quality_alarm.set_reason_text(r"controller-0 GNSS signal quality db below threshold state: signal_quality_db [\d\.]+\s+\(expected: >= [\d\.]+\)")
signal_quality_alarm.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/ttyACM0.ptp=GNSS-signal-quality-db")
alarm_keywords = AlarmListKeywords(ssh_connection)
alarm_keywords.set_timeout_in_seconds(240)
alarm_keywords.wait_for_alarms_to_appear([signal_quality_alarm])
get_logger().log_test_case_step("Lowering signal quality threshold to clear alarm")
ptp_parameter_keywords.system_ptp_instance_parameter_delete("test-monitor", f"signal_quality_db={signal_quality_db}")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", f"signal_quality_db={lowering_signal_quality_db}")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0"])
get_logger().log_test_case_step("Waiting for alarm to clear")
alarm_keywords.set_timeout_in_seconds(240)
alarm_keywords.wait_for_alarms_cleared([signal_quality_alarm])
@mark.p1
@mark.lab_has_gnr_d
def test_gnss_monitoring_parameter_updates(request) -> None:
"""Test dynamic parameter updates and reconfiguration of GNSS monitoring.
This test verifies that monitoring parameters can be updated dynamically
and that the changes are properly applied to the running configuration.
Args:
request (pytest.FixtureRequest): Pytest request fixture for test cleanup registration.
Test Steps:
1. Create monitoring instance with initial parameters
2. Update parameters to new values
3. Verify updated configuration and alarm conditions
"""
lab_connect_keywords = LabConnectionKeywords()
ssh_connection = lab_connect_keywords.get_active_controller_ssh()
ptp_instance_keywords = SystemPTPInstanceKeywords(ssh_connection)
gnss_monitoring_keywords = GnssMonitoringKeywords(ssh_connection)
ptp_parameter_keywords = SystemPTPInstanceParameterKeywords(ssh_connection)
host_ptp_instance_keywords = SystemHostPTPInstanceKeywords(ssh_connection)
alarm_keywords = AlarmListKeywords(ssh_connection)
def cleanup_monitoring_instance():
"""Clean up GNSS monitoring instance."""
get_logger().log_teardown_step("Cleaning up GNSS monitoring instance")
host_ptp_instance_keywords.system_host_ptp_instance_remove_with_error("controller-0", "test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", "satellite_count=150")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", "signal_quality_db=300")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'devices="/dev/ttyACM0"')
ptp_instance_keywords.system_ptp_instance_delete_with_error("test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_inactive()
request.addfinalizer(cleanup_monitoring_instance)
get_logger().log_setup_step("Creating monitoring instance with initial parameters")
gnss_monitoring_keywords.create_monitoring_instance("test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "satellite_count=8")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", 'devices="/dev/ttyACM0"')
host_ptp_instance_keywords.system_host_ptp_instance_assign("controller-0", "test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0"])
get_logger().log_setup_step("Verifying initial configuration")
gnss_monitoring_keywords.verify_monitoring_configuration_file(8, 30, "/dev/ttyACM0")
get_logger().log_test_case_step("Updating ptp instance parameters")
ptp_parameter_keywords.system_ptp_instance_parameter_delete("test-monitor", "satellite_count=8")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "satellite_count=150")
ptp_parameter_keywords.system_ptp_instance_parameter_delete("test-monitor", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "signal_quality_db=300")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0"])
get_logger().log_test_case_step("Waiting for updated configuration")
gnss_monitoring_keywords.wait_for_monitoring_configuration_file(150, 300, "/dev/ttyACM0")
get_logger().log_test_case_step("Testing GNSS data through PTY")
gnss_monitoring_keywords.verify_gnss_pty_data("/dev/ttyACM0")
get_logger().log_test_case_step("Get current satellite count and signal quality for /dev/ttyACM0")
get_gnss_monitoring_data = gnss_monitoring_keywords.get_gnss_monitoring_data("/dev/ttyACM0")
get_satellite_count = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_satellite_count()
get_signal_quality_max = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_signal_quality_max()
get_logger().log_info(f"Current GNSS satellite count for /dev/ttyACM0: {get_satellite_count}")
get_logger().log_info(f"Current GNSS signal quality for /dev/ttyACM0: {get_signal_quality_max}")
get_logger().log_test_case_step("Verifying alarm conditions")
expected_alarms = []
get_logger().log_info(f"ttyACM0 satellite count {get_satellite_count} < 150, expecting satellite alarm")
satellite_alarm = AlarmListObject()
satellite_alarm.set_alarm_id("100.119")
satellite_alarm.set_reason_text(f"controller-0 GNSS satellite count below threshold state: satellite count {get_satellite_count} \(expected: >= 150\)")
satellite_alarm.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/ttyACM0.ptp=GNSS-satellite-count")
expected_alarms.append(satellite_alarm)
get_logger().log_info(f"ttyACM0 signal quality {get_signal_quality_max} < 300, expecting signal quality alarm")
signal_quality_alarm = AlarmListObject()
signal_quality_alarm.set_alarm_id("100.119")
signal_quality_alarm.set_reason_text(r"controller-0 GNSS signal quality db below threshold state: signal_quality_db [\d\.]+\s+\(expected: >= [\d\.]+\)")
signal_quality_alarm.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/ttyACM0.ptp=GNSS-signal-quality-db")
expected_alarms.append(signal_quality_alarm)
# Wait for expected alarms to appear
alarm_keywords.set_timeout_in_seconds(240)
alarm_keywords.wait_for_alarms_to_appear(expected_alarms)
get_logger().log_info("All expected alarms appeared successfully")
# Validate that only expected alarms are present
all_alarms = alarm_keywords.alarm_list()
gnss_alarms = [alarm for alarm in all_alarms if "100.119" in alarm.get_alarm_id()]
validate_equals(len(gnss_alarms), len(expected_alarms), "Number of GNSS alarms should match expected count")
@mark.p1
@mark.lab_has_gnr_d
def test_gnss_monitoring_persistence_after_reboot(request) -> None:
"""Test GNSS monitoring persistence after host operations.
This test verifies that GNSS monitoring configuration and functionality
persist through host lock/unlock operations and system reboots.
Args:
request (pytest.FixtureRequest): Pytest request fixture for test cleanup registration.
Test Steps:
1. Create and configure GNSS monitoring instance
2. Perform host lock/unlock operations
3. Perform system reboot
4. Verify monitoring persists after operations
"""
lab_connect_keywords = LabConnectionKeywords()
ssh_connection = lab_connect_keywords.get_active_controller_ssh()
ptp_instance_keywords = SystemPTPInstanceKeywords(ssh_connection)
gnss_monitoring_keywords = GnssMonitoringKeywords(ssh_connection)
ptp_parameter_keywords = SystemPTPInstanceParameterKeywords(ssh_connection)
host_ptp_instance_keywords = SystemHostPTPInstanceKeywords(ssh_connection)
alarm_keywords = AlarmListKeywords(ssh_connection)
file_keywords = FileKeywords(ssh_connection)
def cleanup_monitoring_instance():
"""Clean up GNSS monitoring instance."""
get_logger().log_teardown_step("Cleaning up GNSS monitoring instance")
host_ptp_instance_keywords.system_host_ptp_instance_remove_with_error("controller-0", "test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", "satellite_count=300")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", "signal_quality_db=600")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'devices="/dev/ttyACM0"')
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'cmdline_opts="-D 7"')
ptp_instance_keywords.system_ptp_instance_delete_with_error("test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_inactive()
request.addfinalizer(cleanup_monitoring_instance)
get_logger().log_setup_step("Creating monitoring instance")
gnss_monitoring_keywords.create_monitoring_instance("test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "satellite_count=300")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "signal_quality_db=600")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", 'devices="/dev/ttyACM0"')
host_ptp_instance_keywords.system_host_ptp_instance_assign("controller-0", "test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0"])
get_logger().log_setup_step("Verifying GNSS monitor configuration file")
gnss_monitoring_keywords.verify_monitoring_configuration_file(300, 600, "/dev/ttyACM0")
get_logger().log_test_case_step("Verifying PTY devices created")
gnss_monitoring_keywords.verify_pty_device_exists("/dev/ttyACM0", True)
get_logger().log_setup_step("Testing GNSS data through PTY")
gnss_monitoring_keywords.verify_gnss_pty_data("/dev/ttyACM0")
get_logger().log_setup_step("Checking current satellite count and signal quality for /dev/ttyACM0")
get_gnss_monitoring_data = gnss_monitoring_keywords.get_gnss_monitoring_data("/dev/ttyACM0")
get_satellite_count = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_satellite_count()
get_signal_quality_max = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_signal_quality_max()
get_logger().log_info(f"Current GNSS satellite count for /dev/ttyACM0: {get_satellite_count}")
get_logger().log_info(f"Current GNSS signal quality for /dev/ttyACM0: {get_signal_quality_max}")
get_logger().log_setup_step("Checking for expected alarms")
expected_alarms = []
get_logger().log_info(f"satellite count {get_satellite_count} < 300, expecting satellite alarm")
satellite_alarm = AlarmListObject()
satellite_alarm.set_alarm_id("100.119")
satellite_alarm.set_reason_text(r"controller-0 GNSS satellite count below threshold state: satellite count [\d]+\s+\(expected: >= 300\)")
satellite_alarm.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/ttyACM0.ptp=GNSS-satellite-count")
expected_alarms.append(satellite_alarm)
get_logger().log_info(f"ttyACM0 signal quality {get_signal_quality_max} < 600, expecting signal quality alarm")
signal_quality_alarm = AlarmListObject()
signal_quality_alarm.set_alarm_id("100.119")
signal_quality_alarm.set_reason_text(r"controller-0 GNSS signal quality db below threshold state: signal_quality_db [\d\.]+\s+\(expected: >= [\d\.]+\)")
signal_quality_alarm.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/ttyACM0.ptp=GNSS-signal-quality-db")
expected_alarms.append(signal_quality_alarm)
# Wait for expected alarms to appear
alarm_keywords.set_timeout_in_seconds(180)
alarm_keywords.wait_for_alarms_to_appear(expected_alarms)
get_logger().log_info("All expected alarms appeared successfully")
get_logger().log_test_case_step("Lock and unlock host")
lock_keywords = SystemHostLockKeywords(ssh_connection)
lock_success = lock_keywords.lock_host("controller-0")
validate_equals(lock_success, True, "Controller should lock successfully")
unlock_success = lock_keywords.unlock_host("controller-0")
validate_equals(unlock_success, True, "Controller should unlock successfully")
get_logger().log_test_case_step("Verifying monitoring instance persists after lock and unlock")
get_host_ptp_instance_for_name = host_ptp_instance_keywords.get_system_host_ptp_instance_list("controller-0").get_host_ptp_instance_for_name("test-monitor")
validate_equals(get_host_ptp_instance_for_name.get_name(), "test-monitor", "Monitoring instance should persist after lock and unlock")
get_logger().log_test_case_step("Verifying GNSS monitor configuration file after lock and unlock")
config_exists = file_keywords.file_exists("/etc/linuxptp/ptpinstance/gnss-monitor-ptp.conf")
validate_equals(config_exists, True, "Configuration file should exist")
gnss_monitoring_keywords.verify_monitoring_configuration_file(300, 600, "/dev/ttyACM0")
get_logger().log_test_case_step("Checking system services after lock and unlock")
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0"])
get_logger().log_test_case_step("Verifying PTY devices created")
gnss_monitoring_keywords.verify_pty_device_exists("/dev/ttyACM0", True)
get_logger().log_test_case_step("Testing GNSS data through PTY after lock and unlock")
gnss_monitoring_keywords.verify_gnss_pty_data("/dev/ttyACM0")
get_logger().log_test_case_step("Checking current satellite count and signal quality for /dev/ttyACM0 after lock and unlock")
get_gnss_monitoring_data = gnss_monitoring_keywords.get_gnss_monitoring_data("/dev/ttyACM0")
get_satellite_count = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_satellite_count()
get_signal_quality_max = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_signal_quality_max()
get_logger().log_info(f"Current GNSS satellite count for /dev/ttyACM0 : {get_satellite_count}")
get_logger().log_info(f"Current GNSS signal quality for /dev/ttyACM0 : {get_signal_quality_max}")
get_logger().log_test_case_step("Checking for expected alarms after lock and unlock")
alarm_keywords.wait_for_alarms_to_appear(expected_alarms)
get_logger().log_info("All expected alarms appeared successfully")
# Validate that only expected alarms are present
all_alarms = alarm_keywords.alarm_list()
gnss_alarms = [alarm for alarm in all_alarms if "100.119" in alarm.get_alarm_id()]
validate_equals(len(gnss_alarms), len(expected_alarms), "Number of GNSS alarms should match expected count")
get_logger().log_test_case_step("Reboot host")
# force reboot the active controller
# get the prev uptime of the host so we can be sure it re-started
pre_uptime_of_host = SystemHostListKeywords(ssh_connection).get_uptime("controller-0")
ssh_connection.send_as_sudo("sudo reboot -f")
reboot_success = SystemHostRebootKeywords(ssh_connection).wait_for_force_reboot("controller-0", pre_uptime_of_host)
validate_equals(reboot_success, True, "Controller should reboot successfully")
get_logger().log_test_case_step("Verifying monitoring instance persists after reboot")
get_host_ptp_instance_for_name = host_ptp_instance_keywords.get_system_host_ptp_instance_list("controller-0").get_host_ptp_instance_for_name("test-monitor")
validate_equals(get_host_ptp_instance_for_name.get_name(), "test-monitor", "Monitoring instance should persist after reboot")
get_logger().log_test_case_step("Verifying GNSS monitor configuration file after reboot")
gnss_monitoring_keywords.verify_monitoring_configuration_file(300, 600, "/dev/ttyACM0")
get_logger().log_test_case_step("Checking system services after reboot")
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0"])
get_logger().log_test_case_step("Verifying PTY devices created")
gnss_monitoring_keywords.verify_pty_device_exists("/dev/ttyACM0", True)
get_logger().log_test_case_step("Testing GNSS data through PTY after reboot")
gnss_monitoring_keywords.verify_gnss_pty_data("/dev/ttyACM0")
get_logger().log_test_case_step("Checking current satellite count and signal quality for /dev/ttyACM0 after reboot")
get_gnss_monitoring_data = gnss_monitoring_keywords.get_gnss_monitoring_data("/dev/ttyACM0")
get_satellite_count = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_satellite_count()
get_signal_quality_max = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_signal_quality_max()
get_logger().log_info(f"Current GNSS satellite count for /dev/ttyACM0 : {get_satellite_count}")
get_logger().log_info(f"Current GNSS signal quality for /dev/ttyACM0 : {get_signal_quality_max}")
get_logger().log_test_case_step("Checking for expected alarms after reboot")
# Wait for expected alarms to appear
alarm_keywords.set_timeout_in_seconds(180)
alarm_keywords.wait_for_alarms_to_appear(expected_alarms)
get_logger().log_info("All expected alarms appeared successfully")
# Validate that only expected alarms are present
all_alarms = alarm_keywords.alarm_list()
gnss_alarms = [alarm for alarm in all_alarms if "100.119" in alarm.get_alarm_id()]
validate_equals(len(gnss_alarms), len(expected_alarms), "Number of GNSS alarms should match expected count")
@mark.p0
@mark.lab_has_gnr_d
def test_gnss_monitoring_instance_removal(request):
"""
GNSS Monitoring Instance Removal
Verify complete cleanup when removing GNSS monitoring instance.
"""
lab_connect_keywords = LabConnectionKeywords()
ssh_connection = lab_connect_keywords.get_active_controller_ssh()
ptp_parameter_keywords = SystemPTPInstanceParameterKeywords(ssh_connection)
gnss_monitoring_keywords = GnssMonitoringKeywords(ssh_connection)
ptp_instance_keywords = SystemPTPInstanceKeywords(ssh_connection)
host_ptp_instance_keywords = SystemHostPTPInstanceKeywords(ssh_connection)
alarm_keywords = AlarmListKeywords(ssh_connection)
get_logger().log_setup_step("Creating monitoring instance")
gnss_monitoring_keywords.create_monitoring_instance("test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "satellite_count=8")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", 'devices="/dev/ttyACM0"')
host_ptp_instance_keywords.system_host_ptp_instance_assign("controller-0", "test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0"])
gnss_monitoring_keywords.verify_pty_device_exists("/dev/ttyACM0", True)
get_logger().log_test_case_step("Removing instance from host")
host_ptp_instance_keywords.system_host_ptp_instance_remove("controller-0", "test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_inactive()
gnss_monitoring_keywords.verify_pty_device_exists("/dev/ttyACM0", False)
get_logger().log_test_case_step("Waiting for alarm to clear")
alarm_keywords.set_timeout_in_seconds(180)
ptp_alarm = AlarmListObject()
ptp_alarm.set_alarm_id("100.119")
alarm_keywords.wait_for_alarms_cleared([ptp_alarm])
get_logger().log_test_case_step("Cleaning up GNSS monitoring instance")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", "satellite_count=8")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'devices="/dev/ttyACM0"')
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'cmdline_opts="-D 7"')
ptp_instance_keywords.system_ptp_instance_delete_with_error("test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_inactive()
error_message = ptp_instance_keywords.get_system_ptp_instance_show_with_error("test-monitor")
validate_str_contains(error_message, "PTP instance not found: test-monitor", "Error message for non-existent instance")
get_logger().log_test_case_step("Waiting for alarm to clear")
alarm_keywords.set_timeout_in_seconds(180)
alarm_keywords.wait_for_alarms_cleared([ptp_alarm])
get_logger().log_test_case_step("Verify PTY devices removed")
gnss_monitoring_keywords.verify_pty_device_exists("/dev/ttyACM0", False)
get_logger().log_info("PTY devices cleaned up")
get_logger().log_test_case_step("Verify configuration file removed")
config_file_check = ssh_connection.send("ls /etc/linuxptp/ptpinstance/gnss-monitor-ptp.conf 2>/dev/null || echo 'not found'")
config_file_check_str = "\n".join(config_file_check) if isinstance(config_file_check, list) else config_file_check
validate_str_contains(config_file_check_str, "not found", "Check: /etc/linuxptp/ptpinstance/gnss-monitor-ptp.conf does not exist")
get_logger().log_info("Configuration files removed")
get_logger().log_test_case_step("Verify ts2phc configuration reverted for nmea_serialport")
gnss_monitoring_keywords.verify_ts2phc_config_serialport("ts1", "/dev/ttyACM0")
@mark.p1
@mark.lab_has_gnr_d
def test_gnss_monitoring_single_instance_per_host(request):
"""
Verify that only one PTP instance is allowed per host and appropriate error handling when attempting to assign multiple instances
Test steps:
1) Ensure existing GNSS monitor instance is assigned
2) Create a second PTP instance
3) Attempt to assign second instance to same host - should fail
4) Confirm only original instance remains active
5) Clean up second instance
"""
lab_connect_keywords = LabConnectionKeywords()
ssh_connection = lab_connect_keywords.get_active_controller_ssh()
ptp_instance_keywords = SystemPTPInstanceKeywords(ssh_connection)
ptp_parameter_keywords = SystemPTPInstanceParameterKeywords(ssh_connection)
host_ptp_instance_keywords = SystemHostPTPInstanceKeywords(ssh_connection)
gnss_monitoring_keywords = GnssMonitoringKeywords(ssh_connection)
def cleanup_monitoring_instances():
"""Clean up GNSS monitoring instances."""
get_logger().log_test_case_step("Cleaning up second instance")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor-2", "satellite_count=8")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor-2", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor-2", 'devices="/dev/ttyACM0 /dev/gnssx"')
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor-2", 'cmdline_opts="-D 7"')
ptp_instance_keywords.system_ptp_instance_delete_with_error("test-monitor-2")
get_logger().log_teardown_step("Cleaning up GNSS monitoring instances")
host_ptp_instance_keywords.system_host_ptp_instance_remove_with_error("controller-0", "test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", "satellite_count=8")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'devices="/dev/ttyACM0 /dev/gnssx"')
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'cmdline_opts="-D 7"')
ptp_instance_keywords.system_ptp_instance_delete_with_error("test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_inactive()
request.addfinalizer(cleanup_monitoring_instances)
get_logger().log_test_case_step("Creating first GNSS monitoring instance")
gnss_monitoring_keywords.create_monitoring_instance("test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "satellite_count=8")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", 'devices="/dev/ttyACM0 /dev/gnssx"')
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", 'cmdline_opts="-D 7"')
host_ptp_instance_keywords.system_host_ptp_instance_assign("controller-0", "test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0", "/dev/gnssx"])
get_logger().log_test_case_step("Verifying first instance assigned successfully")
get_host_ptp_instance_for_name = host_ptp_instance_keywords.get_system_host_ptp_instance_list("controller-0").get_host_ptp_instance_for_name("test-monitor")
validate_equals(get_host_ptp_instance_for_name.get_name(), "test-monitor", "First instance should be assigned")
get_logger().log_test_case_step("Creating second PTP instance")
gnss_monitoring_keywords.create_monitoring_instance("test-monitor-2")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor-2", "satellite_count=8")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor-2", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor-2", 'devices="/dev/ttyACM0 /dev/gnssx"')
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor-2", 'cmdline_opts="-D 7"')
get_logger().log_test_case_step("Attempting to assign second instance to same host - should fail")
error_message = host_ptp_instance_keywords.system_host_ptp_instance_assign_with_error("controller-0", "test-monitor-2")
validate_str_contains(error_message, "gnss-monitor ptp instance already exists on host", "Should get error about existing gnss-monitor instance")
get_logger().log_test_case_step("Confirming only original instance remains active")
gnss_monitoring_instance = ptp_instance_keywords.get_system_ptp_instance_show("test-monitor")
validate_equals(gnss_monitoring_instance.system_ptp_instance_object.get_name(), "test-monitor", "Original instance should remain")
@mark.p0
@mark.lab_has_gnr_d
def test_gnss_monitoring_threshold_alarm_verification(request):
"""
Verify satellite count and signal quality thresholds trigger appropriate alarms when breached
Test Steps:
1) Check current GNSS device metrics
2) Set thresholds above current values
3) Apply updated configuration
4) Verify updated configuration file
5) Check for threshold alarms
6) Verify only valid device PTY exists
"""
lab_connect_keywords = LabConnectionKeywords()
ssh_connection = lab_connect_keywords.get_active_controller_ssh()
ptp_instance_keywords = SystemPTPInstanceKeywords(ssh_connection)
ptp_parameter_keywords = SystemPTPInstanceParameterKeywords(ssh_connection)
host_ptp_instance_keywords = SystemHostPTPInstanceKeywords(ssh_connection)
gnss_monitoring_keywords = GnssMonitoringKeywords(ssh_connection)
alarm_keywords = AlarmListKeywords(ssh_connection)
get_logger().log_setup_step("Creating GNSS monitor")
gnss_monitoring_keywords.create_monitoring_instance("test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "satellite_count=8")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", 'devices="/dev/ttyACM0 /dev/gnssx"')
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", 'cmdline_opts="-D 7"')
host_ptp_instance_keywords.system_host_ptp_instance_assign("controller-0", "test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0", "/dev/gnssx"])
get_logger().log_setup_step("Checking current satellite count and signal quality for /dev/ttyACM0")
get_gnss_monitoring_data = gnss_monitoring_keywords.get_gnss_monitoring_data("/dev/ttyACM0")
get_satellite_count_ttyACM0 = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_satellite_count()
get_signal_quality_max_ttyACM0 = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/ttyACM0").get_signal_quality_max()
get_logger().log_info(f"Current GNSS satellite count for /dev/ttyACM0: {get_satellite_count_ttyACM0}")
get_logger().log_info(f"Current GNSS signal quality for /dev/ttyACM0: {get_signal_quality_max_ttyACM0}")
get_logger().log_setup_step("Checking current satellite count and signal quality for /dev/gnssx")
get_gnss_monitoring_data = gnss_monitoring_keywords.get_gnss_monitoring_data("/dev/gnssx")
get_satellite_count_gnssx = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/gnssx").get_satellite_count()
get_signal_quality_max_gnssx = get_gnss_monitoring_data.get_monitoring_data_for_device("/dev/gnssx").get_signal_quality_max()
get_logger().log_info(f"Current GNSS satellite count for /dev/gnssx: {get_satellite_count_gnssx}")
get_logger().log_info(f"Current GNSS signal quality for /dev/gnssx: {get_signal_quality_max_gnssx}")
# Set thresholds higher than both devices' current values
satellite_count = max(get_satellite_count_gnssx + 30, get_satellite_count_ttyACM0 + 30)
signal_quality_max = max(int(get_signal_quality_max_gnssx) + 50, int(get_signal_quality_max_ttyACM0) + 50)
get_logger().log_info(f"Setting satellite_count threshold to: {satellite_count}")
get_logger().log_info(f"Setting signal_quality_max threshold to: {signal_quality_max}")
def cleanup_monitoring_instance():
"""Clean up GNSS monitoring instance."""
get_logger().log_teardown_step("Cleaning up GNSS monitoring instance")
host_ptp_instance_keywords.system_host_ptp_instance_remove_with_error("controller-0", "test-monitor")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", f"satellite_count={satellite_count}")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", f"signal_quality_db={signal_quality_max}")
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'devices="/dev/ttyACM0 /dev/gnssx"')
ptp_parameter_keywords.system_ptp_instance_parameter_delete_with_error("test-monitor", 'cmdline_opts="-D 7"')
ptp_instance_keywords.system_ptp_instance_delete_with_error("test-monitor")
ptp_instance_keywords.system_ptp_instance_apply()
gnss_monitoring_keywords.wait_for_monitoring_services_inactive()
request.addfinalizer(cleanup_monitoring_instance)
get_logger().log_test_case_step("Set thresholds above current values")
ptp_parameter_keywords.system_ptp_instance_parameter_delete("test-monitor", "satellite_count=8")
ptp_parameter_keywords.system_ptp_instance_parameter_delete("test-monitor", "signal_quality_db=30")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", f"satellite_count={satellite_count}")
ptp_parameter_keywords.system_ptp_instance_parameter_add("test-monitor", f"signal_quality_db={signal_quality_max}")
system_ptp_instance_apply_output = ptp_instance_keywords.system_ptp_instance_apply()
validate_equals(system_ptp_instance_apply_output, "Applying the PTP Instance configuration", "apply PTP instance configuration")
gnss_monitoring_keywords.wait_for_monitoring_services_active(["/dev/ttyACM0", "/dev/gnssx"])
get_logger().log_test_case_step("Verify updated configuration file")
gnss_monitoring_keywords.wait_for_monitoring_configuration_file(satellite_count, signal_quality_max, "/dev/ttyACM0 /dev/gnssx")
get_logger().log_test_case_step("Verifying alarm conditions")
signal_loss_alarm = AlarmListObject()
signal_loss_alarm.set_alarm_id("100.119")
signal_loss_alarm.set_reason_text("controller-0 GNSS signal loss state: signal lock False \(expected: True\)")
signal_loss_alarm.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/gnssx.ptp=GNSS-signal-loss")
satellite_alarm_gnssx = AlarmListObject()
satellite_alarm_gnssx.set_alarm_id("100.119")
satellite_alarm_gnssx.set_reason_text(r"controller-0 GNSS satellite count below threshold state: satellite count [\d]+\s+\(expected: >= [\d]+\)")
satellite_alarm_gnssx.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/gnssx.ptp=GNSS-satellite-count")
signal_quality_alarm_gnssx = AlarmListObject()
signal_quality_alarm_gnssx.set_alarm_id("100.119")
signal_quality_alarm_gnssx.set_reason_text(r"controller-0 GNSS signal quality db below threshold state: signal_quality_db [\d\.]+\s+\(expected: >= [\d\.]+\)")
signal_quality_alarm_gnssx.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/gnssx.ptp=GNSS-signal-quality-db")
satellite_alarm_ttyACM0 = AlarmListObject()
satellite_alarm_ttyACM0.set_alarm_id("100.119")
satellite_alarm_ttyACM0.set_reason_text(r"controller-0 GNSS satellite count below threshold state: satellite count [\d]+\s+\(expected: >= [\d]+\)")
satellite_alarm_ttyACM0.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/ttyACM0.ptp=GNSS-satellite-count")
signal_quality_alarm_ttyACM0 = AlarmListObject()
signal_quality_alarm_ttyACM0.set_alarm_id("100.119")
signal_quality_alarm_ttyACM0.set_reason_text(r"controller-0 GNSS signal quality db below threshold state: signal_quality_db [\d\.]+\s+\(expected: >= [\d\.]+\)")
signal_quality_alarm_ttyACM0.set_entity_id("host=controller-0.gnss-monitor=gnss-monitor-ptp.device_path=/dev/ttyACM0.ptp=GNSS-signal-quality-db")
alarm_keywords.set_timeout_in_seconds(180)
alarm_keywords.wait_for_alarms_to_appear([signal_loss_alarm, satellite_alarm_gnssx, signal_quality_alarm_gnssx, satellite_alarm_ttyACM0, signal_quality_alarm_ttyACM0])
get_logger().log_info("All expected alarms appeared successfully")
get_logger().log_test_case_step("Verify only valid device PTY exists")
gnss_monitoring_keywords.verify_device_exists("/dev/ttyACM0.pty")
gnss_monitoring_keywords.verify_device_exists("/dev/gnssx.pty")
get_logger().log_test_case_step("Verify GNSS data through PTY")
gnss_monitoring_keywords.verify_gnss_pty_data("/dev/ttyACM0")

View File

@@ -39,6 +39,7 @@ markers=
lab_rook_ceph: mark tests that require rook-ceph application applied
lab_is_aio: mark labs without worker nodes
lab_dell_storage: mark tests that require dell-storage application applied
lab_has_gnr_d: mark tests that use specific lab with Granite Rapids-D GNSS
#TODO: add 'lab_has_bmc_ipmi', 'lab_has_bmc_redfish', 'lab_has_bmc_dynamic', and 'lab_bmc_sensor'

View File

@@ -0,0 +1,65 @@
"""Unit tests for GnssMonitorConfParser."""
from keywords.ptp.cat.gnss_monitor_conf_parser import GnssMonitorConfParser
def test_parse_gnss_monitor_config():
"""Test parsing of GNSS monitor configuration output."""
sample_output = ["[global]\n", "##\n", "## Default Data Set\n", "##\n", "devices /dev/ttyACM0 /dev/gnssx\n", "satellite_count 8\n", "signal_quality_db 30\n"]
parser = GnssMonitorConfParser(sample_output)
values_dict = parser.get_output_values_dict()
assert values_dict["devices"] == "/dev/ttyACM0 /dev/gnssx"
assert values_dict["satellite_count"] == "8"
assert values_dict["signal_quality_db"] == "30"
def test_parse_minimal_config():
"""Test parsing minimal configuration."""
minimal_output = ["[global]\n", "devices /dev/ttyACM0\n", "satellite_count 15\n", "signal_quality_db 40\n"]
parser = GnssMonitorConfParser(minimal_output)
values_dict = parser.get_output_values_dict()
assert values_dict["devices"] == "/dev/ttyACM0"
assert values_dict["satellite_count"] == "15"
assert values_dict["signal_quality_db"] == "40"
def test_parse_config_with_prompts():
"""Test parsing configuration with command prompts and passwords."""
output_with_prompts = ["~$ cat /etc/linuxptp/ptpinstance/gnss-monitor-ptp.conf\n", "[global]\n", "Password: \n", "devices /dev/ttyACM0\n", "satellite_count 10\n", "signal_quality_db 25\n"]
parser = GnssMonitorConfParser(output_with_prompts)
values_dict = parser.get_output_values_dict()
# Should ignore prompts and passwords
assert "~$" not in str(values_dict)
assert "Password:" not in str(values_dict)
assert values_dict["devices"] == "/dev/ttyACM0"
assert values_dict["satellite_count"] == "10"
assert values_dict["signal_quality_db"] == "25"
def test_parse_empty_config():
"""Test parsing empty configuration."""
empty_output = ["[global]\n", "##\n", "## Default Data Set\n", "##\n"]
parser = GnssMonitorConfParser(empty_output)
values_dict = parser.get_output_values_dict()
assert len(values_dict) == 0
def test_parse_config_with_spaces_in_values():
"""Test parsing configuration with spaces in device values."""
output_with_spaces = ["[global]\n", "devices /dev/ttyACM0 /dev/gnssx /dev/ttyUSB0\n", "satellite_count 12\n", "signal_quality_db 35\n"]
parser = GnssMonitorConfParser(output_with_spaces)
values_dict = parser.get_output_values_dict()
assert values_dict["devices"] == "/dev/ttyACM0 /dev/gnssx /dev/ttyUSB0"
assert values_dict["satellite_count"] == "12"
assert values_dict["signal_quality_db"] == "35"