PTP: Change overall sync-state behavior
Currently overall sync-state (/sync/sync-status/sync-state) represents degraded (HOLDOVER/FREERUN) when any state is degraded: os-clock-sync- state, ptp-status/lock-state, gnss-status/gnss-sync-status. This commit changes overall sync-state to represent whether the os-clock is synced or not to the timing source. Overall sync-state should only be degraded if the timing sources involved in providing the end-to-end timing is degraded or lost (i.e. it should represent the overall timing chain status). ptp4l instances that perform timing distribution only and are not part of the host timing chain must not impact the overall sync-state. os-clock-state should only represent the status of the OS clock sync state to the primary clock source in an HA configuration. Unit tests has been added and unit test's python interpreter has been changed to python3.9 to inline with base-image's python version (3.9.2). TEST PLAN: PASS: T-GM deployment with two ptp4l instances trigger different events (stop/start ptp4l/phc2sys/ts2phc instances, change clockClass, skew clocks, disable GNSS) overall sync-state is not affected by ptp-inst1 and ptp-inst2 states. PASS: T-BC deployment with two ptp4l instances trigger different events (stop/start ptp4l/phc2sys instances, change clockClass, skew clocks) overall sync-state is not affected by secondary ptp instance state. PASS: Hybrid (T-GM/T-BC) HA deployement: HA with GNSS and PTP source trigger different events overall sync-state is result of chained relation to chosen primary source Story: 2011370 Task: 51774 Change-Id: Ibfb7fa0f9f8ad09584a5f28b60b0e4649976932c Signed-off-by: Tara Nath Subedi <tara.subedi@windriver.com>
This commit is contained in:
parent
3e5e76abf1
commit
5ae719a36e
14
.zuul.yaml
14
.zuul.yaml
@ -9,7 +9,7 @@
|
||||
check:
|
||||
jobs:
|
||||
- openstack-tox-linters
|
||||
- ptp-notification-tox-py36
|
||||
- ptp-notification-tox-py39
|
||||
- k8sapp-ptp-notification-tox-py39
|
||||
- k8sapp-ptp-notification-tox-pylint
|
||||
- k8sapp-ptp-notification-tox-flake8
|
||||
@ -17,7 +17,7 @@
|
||||
gate:
|
||||
jobs:
|
||||
- openstack-tox-linters
|
||||
- ptp-notification-tox-py36
|
||||
- ptp-notification-tox-py39
|
||||
- k8sapp-ptp-notification-tox-py39
|
||||
- k8sapp-ptp-notification-tox-pylint
|
||||
- k8sapp-ptp-notification-tox-flake8
|
||||
@ -27,13 +27,13 @@
|
||||
- stx-ptp-notification-armada-app-upload-git-mirror
|
||||
|
||||
- job:
|
||||
name: ptp-notification-tox-py36
|
||||
parent: tox-py36
|
||||
name: ptp-notification-tox-py39
|
||||
parent: tox-py39
|
||||
description: |
|
||||
Run py36 test for ptp-notification
|
||||
nodeset: ubuntu-bionic
|
||||
Run py39 test for ptp-notification
|
||||
nodeset: debian-bullseye
|
||||
vars:
|
||||
tox_envlist: py36
|
||||
tox_envlist: py39
|
||||
|
||||
- job:
|
||||
name: k8sapp-ptp-notification-tox-py39
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 Wind River Systems, Inc.
|
||||
# Copyright (c) 2021-2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -81,3 +81,8 @@ SOURCE_SYNC_SYNC_STATE = '/sync/sync-status/sync-state'
|
||||
SOURCE_SYNCE_CLOCK_QUALITY = '/sync/synce-status/clock-quality'
|
||||
SOURCE_SYNCE_LOCK_STATE_EXTENDED = '/sync/synce-status/lock-state-extended'
|
||||
SOURCE_SYNCE_LOCK_STATE = '/sync/synce-status/lock-state'
|
||||
|
||||
class ClockSourceType(object):
|
||||
TypePTP = "PTP"
|
||||
TypeGNSS = "GNSS"
|
||||
TypeNA = "NA"
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2022-2023 Wind River Systems, Inc.
|
||||
# Copyright (c) 2022-2023,2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -12,6 +12,7 @@ from abc import ABC, abstractmethod
|
||||
|
||||
from trackingfunctionsdk.common.helpers import constants
|
||||
from trackingfunctionsdk.common.helpers import log_helper
|
||||
from trackingfunctionsdk.common.helpers import ptpsync as utils
|
||||
from trackingfunctionsdk.common.helpers.cgu_handler import CguHandler
|
||||
from trackingfunctionsdk.model.dto.gnssstate import GnssState
|
||||
|
||||
@ -48,6 +49,7 @@ class GnssMonitor(Observer):
|
||||
"GnssMonitor: Unable to determine tsphc_service name from %s"
|
||||
% self.config_file)
|
||||
|
||||
self.set_ptp_devices()
|
||||
# Setup GNSS data
|
||||
self.gnss_cgu_handler = CguHandler(config_file, nmea_serialport,
|
||||
pci_addr, cgu_path)
|
||||
@ -78,6 +80,34 @@ class GnssMonitor(Observer):
|
||||
self.gnss_pps_state = \
|
||||
self.gnss_cgu_handler.cgu_output_parsed['PPS DPLL']['Status']
|
||||
|
||||
def set_ptp_devices(self):
|
||||
ptp_devices = set()
|
||||
phc_interfaces = self._check_config_file_interfaces()
|
||||
for phc_interface in phc_interfaces:
|
||||
ptp_device = utils.get_interface_phc_device(phc_interface)
|
||||
if ptp_device is not None:
|
||||
ptp_devices.add(ptp_device)
|
||||
self.ptp_devices = list(ptp_devices)
|
||||
LOG.debug("TS2PHC PTP devices are %s" % self.ptp_devices)
|
||||
|
||||
def get_ptp_devices(self):
|
||||
return self.ptp_devices
|
||||
|
||||
def _check_config_file_interfaces(self):
|
||||
phc_interfaces = []
|
||||
with open(self.config_file, 'r') as f:
|
||||
config_lines = f.readlines()
|
||||
config_lines = [line.rstrip() for line in config_lines]
|
||||
|
||||
for line in config_lines:
|
||||
# Find the interface value inside the square brackets
|
||||
if re.match(r"^\[.*\]$", line) and line != "[global]":
|
||||
phc_interface = line.strip("[]")
|
||||
LOG.debug("TS2PHC interface is %s" % phc_interface)
|
||||
phc_interfaces.append(phc_interface)
|
||||
|
||||
return phc_interfaces
|
||||
|
||||
def update(self, subject, matched_line) -> None:
|
||||
LOG.info("Kernel event detected. %s" % matched_line)
|
||||
self.set_gnss_status()
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2022-2023 Wind River Systems, Inc.
|
||||
# Copyright (c) 2022-2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -249,26 +249,7 @@ class OsClockMonitor:
|
||||
|
||||
def _get_interface_phc_device(self):
|
||||
"""Determine the phc device for the interface"""
|
||||
pattern = "/hostsys/class/net/" + self.phc_interface + "/device/ptp/*"
|
||||
ptp_device = glob(pattern)
|
||||
if len(ptp_device) == 0:
|
||||
# Try the 0th interface instead, required for some NIC types
|
||||
phc_interface_base = self.phc_interface[:-1] + "0"
|
||||
LOG.info("No ptp device found at %s trying %s instead"
|
||||
% (pattern, phc_interface_base))
|
||||
pattern = "/hostsys/class/net/" + phc_interface_base + \
|
||||
"/device/ptp/*"
|
||||
ptp_device = glob(pattern)
|
||||
if len(ptp_device) == 0:
|
||||
LOG.warning("No ptp device found for base interface at %s"
|
||||
% pattern)
|
||||
return None
|
||||
if len(ptp_device) > 1:
|
||||
LOG.error("More than one ptp device found at %s" % pattern)
|
||||
return None
|
||||
ptp_device = os.path.basename(ptp_device[0])
|
||||
LOG.debug("Found ptp device %s at %s" % (ptp_device, pattern))
|
||||
return ptp_device
|
||||
return utils.get_interface_phc_device(self.phc_interface)
|
||||
|
||||
def get_os_clock_offset(self):
|
||||
"""Get the os CLOCK_REALTIME offset"""
|
||||
@ -327,6 +308,12 @@ class OsClockMonitor:
|
||||
def get_os_clock_state(self):
|
||||
return self._state
|
||||
|
||||
def get_source_ptp_device(self):
|
||||
# PTP device that is disciplining the OS clock
|
||||
# This is also valid in case of HA source devices as
|
||||
# __publish_os_clock_status updates ptp_device.
|
||||
return self.ptp_device
|
||||
|
||||
def os_clock_status(self, holdover_time, freq, sync_state, event_time):
|
||||
current_time = datetime.datetime.utcnow().timestamp()
|
||||
time_in_holdover = None
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 Wind River Systems, Inc.
|
||||
# Copyright (c) 2021-2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -13,6 +13,7 @@
|
||||
#
|
||||
import datetime
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
|
||||
from trackingfunctionsdk.model.dto.ptpstate import PtpState
|
||||
@ -62,12 +63,48 @@ class PtpMonitor:
|
||||
self.phc2sys_service_name = phc2sys_service_name
|
||||
self.holdover_time = int(holdover_time)
|
||||
self.freq = int(freq)
|
||||
self.set_ptp_devices()
|
||||
self.sync_source = constants.ClockSourceType.TypeNA
|
||||
self._ptp_event_time = datetime.datetime.utcnow().timestamp()
|
||||
self._clock_class_event_time = \
|
||||
datetime.datetime.utcnow().timestamp()
|
||||
self.set_ptp_sync_state()
|
||||
self.set_ptp_clock_class()
|
||||
|
||||
def set_ptp_devices(self):
|
||||
ptp_devices = set()
|
||||
phc_interfaces = self._check_config_file_interfaces()
|
||||
for phc_interface in phc_interfaces:
|
||||
ptp_device = utils.get_interface_phc_device(phc_interface)
|
||||
if ptp_device is not None:
|
||||
ptp_devices.add(ptp_device)
|
||||
self.ptp_devices = list(ptp_devices)
|
||||
LOG.debug("PTP4l PTP devices are %s" % self.ptp_devices)
|
||||
|
||||
def get_ptp_devices(self):
|
||||
return self.ptp_devices
|
||||
|
||||
def get_ptp_sync_source(self):
|
||||
return self.sync_source
|
||||
|
||||
def _check_config_file_interfaces(self):
|
||||
phc_interfaces = []
|
||||
with open(self.ptp4l_config, 'r') as f:
|
||||
config_lines = f.readlines()
|
||||
config_lines = [line.rstrip() for line in config_lines]
|
||||
|
||||
for line in config_lines:
|
||||
# Find the interface value inside the square brackets
|
||||
if re.match(r"^\[.*\]$", line) and line not in [
|
||||
"[global]",
|
||||
"[unicast_master_table]",
|
||||
]:
|
||||
phc_interface = line.strip("[]")
|
||||
LOG.debug("PTP4l interface is %s" % phc_interface)
|
||||
phc_interfaces.append(phc_interface)
|
||||
|
||||
return phc_interfaces
|
||||
|
||||
def set_ptp_sync_state(self):
|
||||
new_ptp_sync_event, ptp_sync_state, ptp_event_time = self.ptp_status()
|
||||
if ptp_sync_state != self._ptp_sync_state:
|
||||
@ -132,6 +169,9 @@ class PtpMonitor:
|
||||
# max holdover time is calculated to be in a 'safety' zone
|
||||
max_holdover_time = (self.holdover_time - self.freq * 2)
|
||||
|
||||
previous_sync_source = self.sync_source
|
||||
sync_source = constants.ClockSourceType.TypeNA
|
||||
|
||||
pmc, ptp4l, _, ptp4lconf = \
|
||||
utils.check_critical_resources(self.ptp4l_service_name,
|
||||
self.phc2sys_service_name)
|
||||
@ -141,11 +181,13 @@ class PtpMonitor:
|
||||
self.pmc_query_results, total_ptp_keywords, port_count = \
|
||||
self.ptpsync()
|
||||
try:
|
||||
sync_state = utils.check_results(self.pmc_query_results,
|
||||
total_ptp_keywords, port_count)
|
||||
sync_state, sync_source = utils.check_results(
|
||||
self.pmc_query_results,total_ptp_keywords, port_count
|
||||
)
|
||||
except RuntimeError as err:
|
||||
LOG.warning(err)
|
||||
sync_state = previous_sync_state
|
||||
sync_source = previous_sync_source
|
||||
else:
|
||||
LOG.warning("Missing critical resource: "
|
||||
"PMC %s PTP4L %s PTP4LCONF %s"
|
||||
@ -169,7 +211,16 @@ class PtpMonitor:
|
||||
|
||||
# determine if ptp sync state has changed since the last one
|
||||
LOG.debug("ptp_monitor: sync_state %s, "
|
||||
"previous_sync_state %s" % (sync_state, previous_sync_state))
|
||||
"previous_sync_state %s, "
|
||||
"sync_source %s, "
|
||||
"previous sync_source %s" % (
|
||||
sync_state, previous_sync_state, sync_source, previous_sync_source
|
||||
)
|
||||
)
|
||||
|
||||
# record sync_source of this poll.
|
||||
self.sync_source = sync_source
|
||||
|
||||
if sync_state != previous_sync_state:
|
||||
new_event = True
|
||||
self._ptp_event_time = datetime.datetime.utcnow().timestamp()
|
||||
|
@ -1,6 +1,6 @@
|
||||
#! /usr/bin/python3
|
||||
#
|
||||
# Copyright (c) 2021-2024 Wind River Systems, Inc.
|
||||
# Copyright (c) 2021-2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -16,6 +16,7 @@ import os
|
||||
import re
|
||||
import subprocess
|
||||
import logging
|
||||
from glob import glob
|
||||
from trackingfunctionsdk.common.helpers import constants
|
||||
from trackingfunctionsdk.common.helpers import log_helper
|
||||
|
||||
@ -69,6 +70,9 @@ def check_results(result, total_ptp_keywords, port_count):
|
||||
# sync state is in 'Locked' state and will be overwritten if
|
||||
# it is not the case
|
||||
sync_state = constants.LOCKED_PHC_STATE
|
||||
# sync source is in 'NA' and will be overwritten when source
|
||||
# found to be GNSS or PTP.
|
||||
sync_source = constants.ClockSourceType.TypeNA
|
||||
|
||||
local_gm = False
|
||||
|
||||
@ -77,16 +81,18 @@ def check_results(result, total_ptp_keywords, port_count):
|
||||
LOG.info("Results %s" % result)
|
||||
LOG.info("Results len %s, total_ptp_keywords %s" % (len(result), total_ptp_keywords))
|
||||
raise RuntimeError("PMC results are incomplete, retrying")
|
||||
# determine the current sync state
|
||||
# determine the current sync state and sync source
|
||||
if (result[constants.GM_PRESENT].lower() != constants.GM_IS_PRESENT
|
||||
and result[constants.GRANDMASTER_IDENTITY] != result[constants.CLOCK_IDENTITY]):
|
||||
sync_state = constants.FREERUN_PHC_STATE
|
||||
elif result[constants.GRANDMASTER_IDENTITY] == result[constants.CLOCK_IDENTITY]:
|
||||
local_gm = True
|
||||
sync_source = constants.ClockSourceType.TypeGNSS
|
||||
LOG.debug("Local node is a GM")
|
||||
if not local_gm:
|
||||
for port in range(1, port_count + 1):
|
||||
if result[constants.PORT.format(port)].lower() == constants.SLAVE_MODE:
|
||||
sync_source = constants.ClockSourceType.TypePTP
|
||||
break
|
||||
else:
|
||||
sync_state = constants.FREERUN_PHC_STATE
|
||||
@ -101,7 +107,7 @@ def check_results(result, total_ptp_keywords, port_count):
|
||||
sync_state = constants.FREERUN_PHC_STATE
|
||||
if (result[constants.GM_CLOCK_CLASS] not in ptp4l_clock_class_locked):
|
||||
sync_state = constants.FREERUN_PHC_STATE
|
||||
return sync_state
|
||||
return sync_state, sync_source
|
||||
|
||||
|
||||
def parse_resource_address(resource_address):
|
||||
@ -123,3 +129,27 @@ def format_resource_address(node_name, resource, instance=None):
|
||||
resource_address = resource_address + resource
|
||||
LOG.debug("format_resource_address %s" % resource_address)
|
||||
return resource_address
|
||||
|
||||
|
||||
def get_interface_phc_device(phc_interface):
|
||||
"""Determine the phc device for the interface"""
|
||||
pattern = "/hostsys/class/net/" + phc_interface + "/device/ptp/*"
|
||||
ptp_device = glob(pattern)
|
||||
if len(ptp_device) == 0:
|
||||
# Try the 0th interface instead, required for some NIC types
|
||||
phc_interface_base = phc_interface[:-1] + "0"
|
||||
LOG.info("No ptp device found at %s trying %s instead"
|
||||
% (pattern, phc_interface_base))
|
||||
pattern = "/hostsys/class/net/" + phc_interface_base + \
|
||||
"/device/ptp/*"
|
||||
ptp_device = glob(pattern)
|
||||
if len(ptp_device) == 0:
|
||||
LOG.warning("No ptp device found for base interface at %s"
|
||||
% pattern)
|
||||
return None
|
||||
if len(ptp_device) > 1:
|
||||
LOG.error("More than one ptp device found at %s" % pattern)
|
||||
return None
|
||||
ptp_device = os.path.basename(ptp_device[0])
|
||||
LOG.debug("Found ptp device %s at %s" % (ptp_device, pattern))
|
||||
return ptp_device
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 Wind River Systems, Inc.
|
||||
# Copyright (c) 2021-2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -466,8 +466,51 @@ class PtpWatcherDefault:
|
||||
LOG.debug("Getting os clock status.")
|
||||
return new_event, sync_state, new_event_time
|
||||
|
||||
def __get_overall_sync_state(self, holdover_time, freq, sync_state,
|
||||
last_event_time):
|
||||
def __get_primary_ptp_state(self, ptp_device):
|
||||
# The PTP device itself is being disciplined or not ?
|
||||
# Check which ptp4l instance disciplining this PTP device
|
||||
# disciplining source could be either GNSS or PTP
|
||||
primary_ptp4l = None
|
||||
ptp_state = PtpState.Freerun
|
||||
for ptp4l in self.ptp_monitor_list:
|
||||
# runtime loading of ptp4l config
|
||||
ptp4l.set_ptp_devices()
|
||||
if (
|
||||
ptp_device in ptp4l.get_ptp_devices()
|
||||
and ptp4l.get_ptp_sync_source() != constants.ClockSourceType.TypeNA
|
||||
):
|
||||
primary_ptp4l = ptp4l
|
||||
break
|
||||
|
||||
if primary_ptp4l is not None:
|
||||
_, read_state, _ = primary_ptp4l.get_ptp_sync_state()
|
||||
if read_state == PtpState.Locked:
|
||||
ptp_state = PtpState.Locked
|
||||
|
||||
return primary_ptp4l, ptp_state
|
||||
|
||||
def __get_primary_gnss_state(self, ptp_device):
|
||||
# The PTP device itself is being disciplined or not ?
|
||||
# Check which ts2phc instance disciplining this PTP device
|
||||
primary_gnss = None
|
||||
gnss_state = GnssState.Failure_Nofix
|
||||
for gnss in self.observer_list:
|
||||
# runtime loading of ts2phc config
|
||||
gnss.set_ptp_devices()
|
||||
if ptp_device in gnss.get_ptp_devices():
|
||||
primary_gnss = gnss
|
||||
break
|
||||
|
||||
if primary_gnss is not None:
|
||||
read_state = primary_gnss._state
|
||||
if read_state == GnssState.Synchronized:
|
||||
gnss_state = GnssState.Synchronized
|
||||
|
||||
return primary_gnss, gnss_state
|
||||
|
||||
def __get_overall_sync_state(
|
||||
self, holdover_time, freq, sync_state, last_event_time
|
||||
):
|
||||
new_event = False
|
||||
new_event_time = last_event_time
|
||||
previous_sync_state = sync_state
|
||||
@ -481,45 +524,91 @@ class PtpWatcherDefault:
|
||||
ptp_state = None
|
||||
|
||||
LOG.debug("Getting overall sync state.")
|
||||
for gnss in self.observer_list:
|
||||
if gnss._state == constants.UNKNOWN_PHC_STATE or \
|
||||
gnss._state == GnssState.Failure_Nofix:
|
||||
gnss_state = GnssState.Failure_Nofix
|
||||
elif gnss._state == GnssState.Synchronized and \
|
||||
gnss_state != GnssState.Failure_Nofix:
|
||||
gnss_state = GnssState.Synchronized
|
||||
|
||||
for ptp4l in self.ptp_monitor_list:
|
||||
_, read_state, _ = ptp4l.get_ptp_sync_state()
|
||||
if read_state == PtpState.Holdover or \
|
||||
read_state == PtpState.Freerun or \
|
||||
read_state == constants.UNKNOWN_PHC_STATE:
|
||||
ptp_state = PtpState.Freerun
|
||||
elif read_state == PtpState.Locked and \
|
||||
ptp_state != PtpState.Freerun:
|
||||
ptp_state = PtpState.Locked
|
||||
|
||||
# overall state depends on os_clock_state and single chained gnss/ptp state
|
||||
# Need to figure out which gnss/ptp is disciplining the PHC that syncs os_clock
|
||||
os_clock_state = self.os_clock_monitor.get_os_clock_state()
|
||||
sync_state = OverallClockState.Freerun
|
||||
if os_clock_state is not OsClockState.Freerun:
|
||||
# PTP device that is disciplining the OS clock,
|
||||
# valid even for HA source devices
|
||||
ptp_device = self.os_clock_monitor.get_source_ptp_device()
|
||||
if ptp_device is None:
|
||||
# This may happen in virtualized environments
|
||||
LOG.warning("No PTP device. Defaulting overall state Freerun")
|
||||
else:
|
||||
# What source (gnss or ptp) disciplining the PTP device at the
|
||||
# moment (A PTP device could have both TS2PHC/gnss source and
|
||||
# PTP4l/slave)
|
||||
sync_source = constants.ClockSourceType.TypeNA
|
||||
# any ts2phc instance disciplining the ptp device (source GNSS)
|
||||
primary_gnss, gnss_state = self.__get_primary_gnss_state(ptp_device)
|
||||
if primary_gnss is not None:
|
||||
sync_source = constants.ClockSourceType.TypeGNSS
|
||||
|
||||
if gnss_state is GnssState.Failure_Nofix or \
|
||||
os_clock_state is OsClockState.Freerun or \
|
||||
ptp_state is PtpState.Freerun:
|
||||
sync_state = OverallClockState.Freerun
|
||||
else:
|
||||
sync_state = OverallClockState.Locked
|
||||
# any ptp4l instance disciplining the ptp device (source PTP or GNSS)
|
||||
primary_ptp4l, ptp_state = self.__get_primary_ptp_state(ptp_device)
|
||||
|
||||
# which source: PTP or GNSS
|
||||
# In presence of ptp4l instance disciplining the ptp device, it truly
|
||||
# dictates what source it is using.
|
||||
if primary_ptp4l is not None:
|
||||
sync_source = primary_ptp4l.get_ptp_sync_source()
|
||||
|
||||
ptp4l_instance_and_state = (
|
||||
"NA"
|
||||
if primary_ptp4l is None
|
||||
else (primary_ptp4l.ptp4l_service_name, ptp_state)
|
||||
)
|
||||
ts2phc_instance_and_state = (
|
||||
"NA"
|
||||
if primary_gnss is None
|
||||
else (primary_gnss.ts2phc_service_name, gnss_state)
|
||||
)
|
||||
LOG.debug(
|
||||
f"Overall sync state chaining info:\n"
|
||||
f"os-clock's source ptp-device = {ptp_device}\n"
|
||||
f"ptp-device's sync-source = {sync_source}\n"
|
||||
f"ptp4l-instance-and-state = {ptp4l_instance_and_state}\n"
|
||||
f"ts2phc-instance-and-state = {ts2phc_instance_and_state}"
|
||||
)
|
||||
|
||||
# Based on sync_source that is used to discipline the ptp device,
|
||||
# dependent ts2phc or ptp4l instance's state is chosen.
|
||||
if sync_source == constants.ClockSourceType.TypeNA:
|
||||
# The PTP device is not being disciplined by any PTP4l/TS2PHC instances
|
||||
LOG.warning(
|
||||
"PTP device used by PHC2SYS is not synced/configured on any PTP4l/TS2PHC instances."
|
||||
)
|
||||
|
||||
elif (
|
||||
sync_source == constants.ClockSourceType.TypeGNSS
|
||||
and gnss_state is GnssState.Synchronized
|
||||
):
|
||||
sync_state = OverallClockState.Locked
|
||||
|
||||
elif (
|
||||
sync_source == constants.ClockSourceType.TypePTP
|
||||
and ptp_state is PtpState.Locked
|
||||
):
|
||||
sync_state = OverallClockState.Locked
|
||||
|
||||
if sync_state == OverallClockState.Freerun:
|
||||
if previous_sync_state in [
|
||||
constants.UNKNOWN_PHC_STATE,
|
||||
constants.FREERUN_PHC_STATE]:
|
||||
constants.UNKNOWN_PHC_STATE,
|
||||
constants.FREERUN_PHC_STATE,
|
||||
]:
|
||||
sync_state = OverallClockState.Freerun
|
||||
elif previous_sync_state == constants.LOCKED_PHC_STATE:
|
||||
sync_state = OverallClockState.Holdover
|
||||
elif previous_sync_state == constants.HOLDOVER_PHC_STATE and \
|
||||
time_in_holdover < max_holdover_time:
|
||||
LOG.debug("Overall sync: Time in holdover is %s "
|
||||
"Max time in holdover is %s"
|
||||
% (time_in_holdover, max_holdover_time))
|
||||
elif (
|
||||
previous_sync_state == constants.HOLDOVER_PHC_STATE
|
||||
and time_in_holdover < max_holdover_time
|
||||
):
|
||||
LOG.debug(
|
||||
"Overall sync: Time in holdover is %s "
|
||||
"Max time in holdover is %s" % (time_in_holdover, max_holdover_time)
|
||||
)
|
||||
sync_state = OverallClockState.Holdover
|
||||
else:
|
||||
sync_state = OverallClockState.Freerun
|
||||
|
@ -0,0 +1,517 @@
|
||||
#
|
||||
# Copyright (c) 2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
import mock
|
||||
import unittest
|
||||
import json
|
||||
import time
|
||||
|
||||
from dataclasses import dataclass
|
||||
from trackingfunctionsdk.common.helpers import constants
|
||||
from trackingfunctionsdk.model.dto.osclockstate import OsClockState
|
||||
from trackingfunctionsdk.model.dto.overallclockstate import OverallClockState
|
||||
from trackingfunctionsdk.model.dto.ptpstate import PtpState
|
||||
from trackingfunctionsdk.model.dto.gnssstate import GnssState
|
||||
from trackingfunctionsdk.services.daemon import PtpWatcherDefault
|
||||
|
||||
context = {
|
||||
"THIS_NAMESPACE": "notification",
|
||||
"THIS_NODE_NAME": "controller-0",
|
||||
"THIS_POD_IP": "172.16.192.71",
|
||||
"REGISTRATION_TRANSPORT_ENDPOINT": "rabbit://admin:admin@registration.notification.svc.cluster.local:5672",
|
||||
"NOTIFICATION_TRANSPORT_ENDPOINT": "rabbit://admin:admin@172.16.192.71:5672",
|
||||
"GNSS_CONFIGS": [
|
||||
"/ptp/linuxptp/ptpinstance/ts2phc-ts1.conf",
|
||||
"/ptp/linuxptp/ptpinstance/ts2phc-ts2.conf",
|
||||
],
|
||||
"PHC2SYS_CONFIG": "/ptp/linuxptp/ptpinstance/phc2sys-phc-inst1.conf",
|
||||
"PHC2SYS_SERVICE_NAME": "phc-inst1",
|
||||
"PTP4L_CONFIGS": [
|
||||
"/ptp/linuxptp/ptpinstance/ptp4l-ptp-inst2.conf",
|
||||
"/ptp/linuxptp/ptpinstance/ptp4l-ptp-inst1.conf",
|
||||
],
|
||||
"GNSS_INSTANCES": ["ts1", "ts2"],
|
||||
"PTP4L_INSTANCES": ["ptp-inst2", "ptp-inst1"],
|
||||
"ptptracker_context": {"device_simulated": "false", "holdover_seconds": "15"},
|
||||
"gnsstracker_context": {"holdover_seconds": 30},
|
||||
"osclocktracker_context": {"holdover_seconds": "15"},
|
||||
"overalltracker_context": {"holdover_seconds": "15"},
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class OsClockData:
|
||||
sync_state: str = OsClockState.Freerun
|
||||
sync_source: str = "ptp0"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PTP4lData:
|
||||
ptp_devices: list[str]
|
||||
sync_state: str = PtpState.Freerun
|
||||
sync_source: str = constants.ClockSourceType.TypePTP
|
||||
|
||||
|
||||
@dataclass
|
||||
class ts2phcData:
|
||||
ptp_devices: list[str]
|
||||
sync_state: str = GnssState.Failure_Nofix
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestData:
|
||||
osclock: OsClockData
|
||||
ptp4l: list[PTP4lData]
|
||||
ts2phc: list[ts2phcData]
|
||||
|
||||
|
||||
class DaemonTests(unittest.TestCase):
|
||||
|
||||
@mock.patch("trackingfunctionsdk.services.daemon.PtpMonitor")
|
||||
@mock.patch("trackingfunctionsdk.services.daemon.OsClockMonitor")
|
||||
@mock.patch("trackingfunctionsdk.services.daemon.GnssMonitor")
|
||||
def _setup(self, gnssmonitor_mock, osclockmonitor_mock, ptpmonitor_mock):
|
||||
event = None
|
||||
|
||||
sqlalchemy_conf = {
|
||||
"url": "sqlite:///apiserver.db",
|
||||
"echo": False,
|
||||
"echo_pool": False,
|
||||
"pool_recycle": 3600,
|
||||
"encoding": "utf-8",
|
||||
}
|
||||
sqlalchemy_conf_json = json.dumps(sqlalchemy_conf)
|
||||
daemon_context_json = json.dumps(context)
|
||||
|
||||
# distint mock class instances, to have distinct mock method on instance basis
|
||||
gnssmonitor_mock.side_effect = [
|
||||
mock.Mock(name=item) for item in context["GNSS_CONFIGS"]
|
||||
]
|
||||
ptpmonitor_mock.side_effect = [
|
||||
mock.Mock(name=item) for item in context["PTP4L_CONFIGS"]
|
||||
]
|
||||
|
||||
self.worker = PtpWatcherDefault(
|
||||
event, sqlalchemy_conf_json, daemon_context_json
|
||||
)
|
||||
|
||||
self.osclockmonitor_mock_instance = self.worker.os_clock_monitor
|
||||
self.ptpmonitor_mock_instances = self.worker.ptp_monitor_list
|
||||
self.gnssmonitor_mock_instances = self.worker.observer_list
|
||||
|
||||
self.assertEqual(
|
||||
len(self.gnssmonitor_mock_instances), len(context["GNSS_CONFIGS"])
|
||||
)
|
||||
self.assertEqual(
|
||||
len(self.ptpmonitor_mock_instances), len(context["PTP4L_CONFIGS"])
|
||||
)
|
||||
|
||||
def _test__get_overall_sync_state(self, testdata, expected):
|
||||
holdover_time = float(context["overalltracker_context"]["holdover_seconds"])
|
||||
freq = 2
|
||||
sync_state = OverallClockState.Freerun
|
||||
last_event_time = time.time()
|
||||
|
||||
self.osclockmonitor_mock_instance.get_source_ptp_device.return_value = (
|
||||
testdata.osclock.sync_source
|
||||
)
|
||||
self.osclockmonitor_mock_instance.get_os_clock_state.return_value = (
|
||||
testdata.osclock.sync_state
|
||||
)
|
||||
# test mocking as expected or not.
|
||||
self.assertEqual(
|
||||
self.worker.os_clock_monitor.get_source_ptp_device(),
|
||||
testdata.osclock.sync_source,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.worker.os_clock_monitor.get_os_clock_state(),
|
||||
testdata.osclock.sync_state,
|
||||
)
|
||||
|
||||
for i, gnssmonitor_mock_instance in enumerate(self.gnssmonitor_mock_instances):
|
||||
gnssmonitor_mock_instance.get_ptp_devices.return_value = testdata.ts2phc[
|
||||
i
|
||||
].ptp_devices
|
||||
gnssmonitor_mock_instance._state = testdata.ts2phc[i].sync_state
|
||||
# test mocking as expected or not.
|
||||
self.assertEqual(
|
||||
self.gnssmonitor_mock_instances[0].get_ptp_devices(),
|
||||
testdata.ts2phc[0].ptp_devices,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.gnssmonitor_mock_instances[0]._state, testdata.ts2phc[0].sync_state
|
||||
)
|
||||
self.assertEqual(
|
||||
self.gnssmonitor_mock_instances[1].get_ptp_devices(),
|
||||
testdata.ts2phc[1].ptp_devices,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.gnssmonitor_mock_instances[1]._state, testdata.ts2phc[1].sync_state
|
||||
)
|
||||
|
||||
for i, ptpmonitor_mock_instance in enumerate(self.ptpmonitor_mock_instances):
|
||||
ptpmonitor_mock_instance.get_ptp_devices.return_value = testdata.ptp4l[
|
||||
i
|
||||
].ptp_devices
|
||||
ptpmonitor_mock_instance.get_ptp_sync_state.return_value = (
|
||||
None,
|
||||
testdata.ptp4l[i].sync_state,
|
||||
None,
|
||||
)
|
||||
ptpmonitor_mock_instance.get_ptp_sync_source.return_value = testdata.ptp4l[
|
||||
i
|
||||
].sync_source
|
||||
# test mocking as expected or not.
|
||||
self.assertEqual(
|
||||
self.ptpmonitor_mock_instances[0].get_ptp_devices(),
|
||||
testdata.ptp4l[0].ptp_devices,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.ptpmonitor_mock_instances[0].get_ptp_sync_state(),
|
||||
(None, testdata.ptp4l[0].sync_state, None),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.ptpmonitor_mock_instances[0].get_ptp_sync_source(),
|
||||
testdata.ptp4l[0].sync_source,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.ptpmonitor_mock_instances[1].get_ptp_devices(),
|
||||
testdata.ptp4l[1].ptp_devices,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.ptpmonitor_mock_instances[1].get_ptp_sync_state(),
|
||||
(None, testdata.ptp4l[1].sync_state, None),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.ptpmonitor_mock_instances[1].get_ptp_sync_source(),
|
||||
testdata.ptp4l[1].sync_source,
|
||||
)
|
||||
|
||||
new_event, sync_state, new_event_time = (
|
||||
self.worker._PtpWatcherDefault__get_overall_sync_state(
|
||||
holdover_time, freq, sync_state, last_event_time
|
||||
)
|
||||
)
|
||||
# overall sync state assertion
|
||||
self.assertEqual(sync_state, expected)
|
||||
|
||||
def test__get_overall_sync_state__all_are_locked__overall_locked(self):
|
||||
# when all are locked state -- overall state would be locked
|
||||
self._setup()
|
||||
osclockdata = OsClockData(sync_state=OsClockState.Locked, sync_source="ptp0")
|
||||
|
||||
ptp4ldata0 = PTP4lData(
|
||||
ptp_devices=["ptp0"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
ptp4ldata1 = PTP4lData(
|
||||
ptp_devices=["ptp1"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
|
||||
ts2phcdata0 = ts2phcData(
|
||||
ptp_devices=["ptp0"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
ts2phcdata1 = ts2phcData(
|
||||
ptp_devices=["ptp1"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
|
||||
testdata = TestData(
|
||||
osclock=osclockdata,
|
||||
ptp4l=[ptp4ldata0, ptp4ldata1],
|
||||
ts2phc=[ts2phcdata0, ts2phcdata1],
|
||||
)
|
||||
expected = OverallClockState.Locked
|
||||
self._test__get_overall_sync_state(testdata, expected)
|
||||
|
||||
def test__get_overall_sync_state__osclock_freerun__overall_freerun(self):
|
||||
# when osclock is on freerun, and others are on locked state -- overall
|
||||
# state would be freerun
|
||||
self._setup()
|
||||
osclockdata = OsClockData(sync_state=OsClockState.Freerun, sync_source="ptp0")
|
||||
|
||||
ptp4ldata0 = PTP4lData(
|
||||
ptp_devices=["ptp0"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
ptp4ldata1 = PTP4lData(
|
||||
ptp_devices=["ptp1"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
|
||||
ts2phcdata0 = ts2phcData(
|
||||
ptp_devices=["ptp0"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
ts2phcdata1 = ts2phcData(
|
||||
ptp_devices=["ptp1"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
|
||||
testdata = TestData(
|
||||
osclock=osclockdata,
|
||||
ptp4l=[ptp4ldata0, ptp4ldata1],
|
||||
ts2phc=[ts2phcdata0, ts2phcdata1],
|
||||
)
|
||||
expected = OverallClockState.Freerun
|
||||
self._test__get_overall_sync_state(testdata, expected)
|
||||
|
||||
def test__get_overall_sync_state__ptp4l_ptp0_freerun__overall_freerun(self):
|
||||
# when chained ptp4l ptp0 sync_state is freerun -- overall state would be freerun
|
||||
self._setup()
|
||||
osclockdata = OsClockData(sync_state=OsClockState.Locked, sync_source="ptp0")
|
||||
|
||||
ptp4ldata0 = PTP4lData(
|
||||
ptp_devices=["ptp0"],
|
||||
sync_state=PtpState.Freerun,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
ptp4ldata1 = PTP4lData(
|
||||
ptp_devices=["ptp1"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
|
||||
ts2phcdata0 = ts2phcData(
|
||||
ptp_devices=["ptp0"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
ts2phcdata1 = ts2phcData(
|
||||
ptp_devices=["ptp1"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
|
||||
testdata = TestData(
|
||||
osclock=osclockdata,
|
||||
ptp4l=[ptp4ldata0, ptp4ldata1],
|
||||
ts2phc=[ts2phcdata0, ts2phcdata1],
|
||||
)
|
||||
expected = OverallClockState.Freerun
|
||||
self._test__get_overall_sync_state(testdata, expected)
|
||||
|
||||
def test__get_overall_sync_state__ptp4l_ptp0_locked__overall_locked(self):
|
||||
# when chained ptp4l ptp0 sync_state is locked -- overall state would be locked
|
||||
self._setup()
|
||||
osclockdata = OsClockData(sync_state=OsClockState.Locked, sync_source="ptp0")
|
||||
|
||||
ptp4ldata0 = PTP4lData(
|
||||
ptp_devices=["ptp0"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
ptp4ldata1 = PTP4lData(
|
||||
ptp_devices=["ptp1"],
|
||||
sync_state=PtpState.Freerun,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
|
||||
ts2phcdata0 = ts2phcData(
|
||||
ptp_devices=["ptp0"], sync_state=GnssState.Failure_Nofix
|
||||
)
|
||||
ts2phcdata1 = ts2phcData(
|
||||
ptp_devices=["ptp1"], sync_state=GnssState.Failure_Nofix
|
||||
)
|
||||
|
||||
testdata = TestData(
|
||||
osclock=osclockdata,
|
||||
ptp4l=[ptp4ldata0, ptp4ldata1],
|
||||
ts2phc=[ts2phcdata0, ts2phcdata1],
|
||||
)
|
||||
expected = OverallClockState.Locked
|
||||
self._test__get_overall_sync_state(testdata, expected)
|
||||
|
||||
def test__get_overall_sync_state__ts2phc_ptp0_freerun__overall_freerun(self):
|
||||
# when chained ts2phc ptp0 sync_state is freerun -- overall state would be freerun
|
||||
self._setup()
|
||||
osclockdata = OsClockData(sync_state=OsClockState.Locked, sync_source="ptp0")
|
||||
|
||||
ptp4ldata0 = PTP4lData(
|
||||
ptp_devices=["ptp0"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypeGNSS,
|
||||
)
|
||||
ptp4ldata1 = PTP4lData(
|
||||
ptp_devices=["ptp1"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
|
||||
ts2phcdata0 = ts2phcData(
|
||||
ptp_devices=["ptp0"], sync_state=GnssState.Failure_Nofix
|
||||
)
|
||||
ts2phcdata1 = ts2phcData(
|
||||
ptp_devices=["ptp1"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
|
||||
testdata = TestData(
|
||||
osclock=osclockdata,
|
||||
ptp4l=[ptp4ldata0, ptp4ldata1],
|
||||
ts2phc=[ts2phcdata0, ts2phcdata1],
|
||||
)
|
||||
expected = OverallClockState.Freerun
|
||||
self._test__get_overall_sync_state(testdata, expected)
|
||||
|
||||
def test__get_overall_sync_state__ts2phc_ptp0_locked__overall_locked(self):
|
||||
# when chained ts2phc ptp0 sync_state is locked -- overall state would be locked
|
||||
self._setup()
|
||||
osclockdata = OsClockData(sync_state=OsClockState.Locked, sync_source="ptp0")
|
||||
|
||||
ptp4ldata0 = PTP4lData(
|
||||
ptp_devices=["ptp0"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypeGNSS,
|
||||
)
|
||||
ptp4ldata1 = PTP4lData(
|
||||
ptp_devices=["ptp1"],
|
||||
sync_state=PtpState.Freerun,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
|
||||
ts2phcdata0 = ts2phcData(
|
||||
ptp_devices=["ptp0"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
ts2phcdata1 = ts2phcData(
|
||||
ptp_devices=["ptp1"], sync_state=GnssState.Failure_Nofix
|
||||
)
|
||||
|
||||
testdata = TestData(
|
||||
osclock=osclockdata,
|
||||
ptp4l=[ptp4ldata0, ptp4ldata1],
|
||||
ts2phc=[ts2phcdata0, ts2phcdata1],
|
||||
)
|
||||
expected = OverallClockState.Locked
|
||||
self._test__get_overall_sync_state(testdata, expected)
|
||||
|
||||
def test__get_overall_sync_state__ts2phc_ptp0_locked_no_ptp4l_for_ptp0__overall_locked(
|
||||
self,
|
||||
):
|
||||
# when chained ts2phc ptp0 sync_state is locked -- overall state would be locked
|
||||
# In this case there are no ptp4l instances with ptp0
|
||||
self._setup()
|
||||
osclockdata = OsClockData(sync_state=OsClockState.Locked, sync_source="ptp0")
|
||||
|
||||
ptp4ldata0 = PTP4lData(
|
||||
ptp_devices=["ptpx"],
|
||||
sync_state=PtpState.Freerun,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
ptp4ldata1 = PTP4lData(
|
||||
ptp_devices=["ptp1"],
|
||||
sync_state=PtpState.Freerun,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
|
||||
ts2phcdata0 = ts2phcData(
|
||||
ptp_devices=["ptp0"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
ts2phcdata1 = ts2phcData(
|
||||
ptp_devices=["ptp1"], sync_state=GnssState.Failure_Nofix
|
||||
)
|
||||
|
||||
testdata = TestData(
|
||||
osclock=osclockdata,
|
||||
ptp4l=[ptp4ldata0, ptp4ldata1],
|
||||
ts2phc=[ts2phcdata0, ts2phcdata1],
|
||||
)
|
||||
expected = OverallClockState.Locked
|
||||
self._test__get_overall_sync_state(testdata, expected)
|
||||
|
||||
def test__get_overall_sync_state__no_source_for_ptp0__overall_freerun(self):
|
||||
# when chained ptp4l ptp0 sync_source NA (neither gnss nor ptp) -- overall
|
||||
# state would be freerun
|
||||
# In this case there are no ts2phc instances with ptp0
|
||||
self._setup()
|
||||
osclockdata = OsClockData(sync_state=OsClockState.Locked, sync_source="ptp0")
|
||||
|
||||
# In this case, practically ptp0's ptp4l instance sync_state would be
|
||||
# PtpState.Freerun, as there is no sync source. But still using locked state.
|
||||
ptp4ldata0 = PTP4lData(
|
||||
ptp_devices=["ptp0"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypeNA,
|
||||
)
|
||||
ptp4ldata1 = PTP4lData(
|
||||
ptp_devices=["ptp1"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
|
||||
ts2phcdata0 = ts2phcData(
|
||||
ptp_devices=["ptpx"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
ts2phcdata1 = ts2phcData(
|
||||
ptp_devices=["ptp1"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
|
||||
testdata = TestData(
|
||||
osclock=osclockdata,
|
||||
ptp4l=[ptp4ldata0, ptp4ldata1],
|
||||
ts2phc=[ts2phcdata0, ts2phcdata1],
|
||||
)
|
||||
expected = OverallClockState.Freerun
|
||||
self._test__get_overall_sync_state(testdata, expected)
|
||||
|
||||
def test__get_overall_sync_state__no_backtrack_for_ptp0__overall_freerun(self):
|
||||
# when chained ptp0 is not included neither on ptp4l nor ts2phc -- overall
|
||||
# state would be freerun
|
||||
self._setup()
|
||||
osclockdata = OsClockData(sync_state=OsClockState.Locked, sync_source="ptp0")
|
||||
|
||||
ptp4ldata0 = PTP4lData(
|
||||
ptp_devices=["ptpx"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
ptp4ldata1 = PTP4lData(
|
||||
ptp_devices=["ptp1"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
|
||||
ts2phcdata0 = ts2phcData(
|
||||
ptp_devices=["ptpx"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
ts2phcdata1 = ts2phcData(
|
||||
ptp_devices=["ptp1"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
|
||||
testdata = TestData(
|
||||
osclock=osclockdata,
|
||||
ptp4l=[ptp4ldata0, ptp4ldata1],
|
||||
ts2phc=[ts2phcdata0, ts2phcdata1],
|
||||
)
|
||||
expected = OverallClockState.Freerun
|
||||
self._test__get_overall_sync_state(testdata, expected)
|
||||
|
||||
def test__get_overall_sync_state__os_clock_no_ptp_device__overall_freerun(self):
|
||||
# when there is no sync_source e.g. ptp0 for os_clock -- overall state would be freerun
|
||||
self._setup()
|
||||
osclockdata = OsClockData(sync_state=OsClockState.Locked, sync_source=None)
|
||||
|
||||
ptp4ldata0 = PTP4lData(
|
||||
ptp_devices=["ptp0"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
ptp4ldata1 = PTP4lData(
|
||||
ptp_devices=["ptp1"],
|
||||
sync_state=PtpState.Locked,
|
||||
sync_source=constants.ClockSourceType.TypePTP,
|
||||
)
|
||||
|
||||
ts2phcdata0 = ts2phcData(
|
||||
ptp_devices=["ptp0"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
ts2phcdata1 = ts2phcData(
|
||||
ptp_devices=["ptp1"], sync_state=GnssState.Synchronized
|
||||
)
|
||||
|
||||
testdata = TestData(
|
||||
osclock=osclockdata,
|
||||
ptp4l=[ptp4ldata0, ptp4ldata1],
|
||||
ts2phc=[ts2phcdata0, ts2phcdata1],
|
||||
)
|
||||
expected = OverallClockState.Freerun
|
||||
self._test__get_overall_sync_state(testdata, expected)
|
@ -0,0 +1,45 @@
|
||||
#
|
||||
# Copyright (c) 2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
import mock
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from trackingfunctionsdk.common.helpers.gnss_monitor import GnssMonitor
|
||||
|
||||
testpath = os.environ.get("TESTPATH", "")
|
||||
|
||||
|
||||
class GnssMonitorTests(unittest.TestCase):
|
||||
|
||||
def test_check_config_file_interfaces(self):
|
||||
cgu_path = testpath + "test_input_files/mock_cgu_output_logan_beach"
|
||||
gnss_config = testpath + "test_input_files/ts2phc_valid.conf"
|
||||
self.gnssmon = GnssMonitor(gnss_config, cgu_path = cgu_path)
|
||||
self.assertEqual(self.gnssmon._check_config_file_interfaces(), ['ens1f0', 'ens2f0'])
|
||||
|
||||
def test_set_ptp_devices(self):
|
||||
cgu_path = testpath + "test_input_files/mock_cgu_output_logan_beach"
|
||||
gnss_config = testpath + "test_input_files/ts2phc_valid.conf"
|
||||
with mock.patch('trackingfunctionsdk.common.helpers.ptpsync.glob',
|
||||
return_value=[]):
|
||||
self.gnssmon = GnssMonitor(gnss_config, cgu_path = cgu_path)
|
||||
self.assertEqual(self.gnssmon.get_ptp_devices(),[])
|
||||
|
||||
with mock.patch('trackingfunctionsdk.common.helpers.ptpsync.glob',
|
||||
side_effect=[['/hostsys/class/net/ens1f0/device/ptp/ptp0'],
|
||||
['/hostsys/class/net/ens2f0/device/ptp/ptp1']
|
||||
]):
|
||||
self.gnssmon.set_ptp_devices()
|
||||
|
||||
self.assertEqual(set(self.gnssmon.get_ptp_devices()),set(['ptp0','ptp1']))
|
||||
|
||||
with mock.patch('trackingfunctionsdk.common.helpers.ptpsync.glob',
|
||||
side_effect=[['/hostsys/class/net/ens1f0/device/ptp/ptp0'],
|
||||
['/hostsys/class/net/ens2f0/device/ptp/ptp0']
|
||||
]):
|
||||
self.gnssmon.set_ptp_devices()
|
||||
|
||||
self.assertEqual(self.gnssmon.get_ptp_devices(),['ptp0'])
|
@ -0,0 +1,35 @@
|
||||
[global]
|
||||
##
|
||||
## Default Data Set
|
||||
##
|
||||
boundary_clock_jbod 1
|
||||
clock_servo linreg
|
||||
delay_mechanism E2E
|
||||
domainNumber 24
|
||||
logAnnounceInterval -3
|
||||
logMinDelayReqInterval -4
|
||||
logSyncInterval -4
|
||||
message_tag ptp-inst1
|
||||
network_transport L2
|
||||
summary_interval 6
|
||||
time_stamping hardware
|
||||
tx_timestamp_timeout 700
|
||||
uds_address /var/run/ptp4l-ptp-inst1
|
||||
|
||||
|
||||
|
||||
[enp81s0f3]
|
||||
##
|
||||
## Associated interface: oam1
|
||||
##
|
||||
|
||||
|
||||
[enp81s0f4]
|
||||
##
|
||||
|
||||
[unicast_master_table]
|
||||
table_id 1
|
||||
logQueryInterval 2
|
||||
UDPv4 192.168.1.11
|
||||
UDPv4 192.168.2.22
|
||||
UDPv4 192.168.3.33
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2022-2023 Wind River Systems, Inc.
|
||||
# Copyright (c) 2022-2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -58,7 +58,7 @@ class OsClockMonitorTests(unittest.TestCase):
|
||||
mo.side_effect = handlers
|
||||
self.assertEqual(self.clockmon._get_phc2sys_command_line_option("/var/run/", "-s"), None)
|
||||
|
||||
@mock.patch('trackingfunctionsdk.common.helpers.os_clock_monitor.glob',
|
||||
@mock.patch('trackingfunctionsdk.common.helpers.ptpsync.glob',
|
||||
side_effect=[['/hostsys/class/net/ens1f0/device/ptp/ptp0'],
|
||||
['/hostsys/class/net/ens1f0/device/ptp/ptp0',
|
||||
'/hostsys/class/net/ens1f0/device/ptp/ptp1'],
|
||||
|
@ -0,0 +1,70 @@
|
||||
#
|
||||
# Copyright (c) 2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
import mock
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from trackingfunctionsdk.common.helpers.ptp_monitor import PtpMonitor
|
||||
|
||||
testpath = os.environ.get("TESTPATH", "")
|
||||
|
||||
holdover_seconds = 15
|
||||
poll_freq_seconds = 15
|
||||
phc2sys_service_name = "phc-inst1"
|
||||
ptp4l_instance = "ptp-inst1"
|
||||
|
||||
|
||||
class PtpMonitorTests(unittest.TestCase):
|
||||
|
||||
def test_check_config_file_interfaces(self):
|
||||
self.ptpmon = PtpMonitor(
|
||||
ptp4l_instance,
|
||||
holdover_seconds,
|
||||
poll_freq_seconds,
|
||||
phc2sys_service_name,
|
||||
init=False,
|
||||
)
|
||||
self.ptpmon.ptp4l_config = testpath + "test_input_files/ptp4l-ptp-inst1.conf"
|
||||
self.assertEqual(
|
||||
self.ptpmon._check_config_file_interfaces(), ["enp81s0f3", "enp81s0f4"]
|
||||
)
|
||||
|
||||
def test_set_ptp_devices(self):
|
||||
self.ptpmon = PtpMonitor(
|
||||
ptp4l_instance,
|
||||
holdover_seconds,
|
||||
poll_freq_seconds,
|
||||
phc2sys_service_name,
|
||||
init=False,
|
||||
)
|
||||
self.ptpmon.ptp4l_config = testpath + "test_input_files/ptp4l-ptp-inst1.conf"
|
||||
|
||||
with mock.patch(
|
||||
"trackingfunctionsdk.common.helpers.ptpsync.glob", return_value=[]
|
||||
):
|
||||
self.ptpmon.set_ptp_devices()
|
||||
self.assertEqual(self.ptpmon.get_ptp_devices(), [])
|
||||
|
||||
with mock.patch(
|
||||
"trackingfunctionsdk.common.helpers.ptpsync.glob",
|
||||
side_effect=[
|
||||
["/hostsys/class/net/enp81s0f3/device/ptp/ptp0"],
|
||||
["/hostsys/class/net/enp81s0f4/device/ptp/ptp1"],
|
||||
],
|
||||
):
|
||||
self.ptpmon.set_ptp_devices()
|
||||
self.assertEqual(set(self.ptpmon.get_ptp_devices()), set(["ptp0", "ptp1"]))
|
||||
|
||||
with mock.patch(
|
||||
"trackingfunctionsdk.common.helpers.ptpsync.glob",
|
||||
side_effect=[
|
||||
["/hostsys/class/net/enp81s0f3/device/ptp/ptp0"],
|
||||
["/hostsys/class/net/enp81s0f4/device/ptp/ptp0"],
|
||||
],
|
||||
):
|
||||
self.ptpmon.set_ptp_devices()
|
||||
|
||||
self.assertEqual(self.ptpmon.get_ptp_devices(), ["ptp0"])
|
6
tox.ini
6
tox.ini
@ -1,5 +1,5 @@
|
||||
[tox]
|
||||
envlist = linters,py36
|
||||
envlist = linters,py39
|
||||
minversion = 2.3
|
||||
skipsdist = True
|
||||
sitepackages=False
|
||||
@ -18,8 +18,8 @@ deps =
|
||||
allowlist_externals =
|
||||
bash
|
||||
|
||||
[testenv:py36]
|
||||
basepython = python3.6
|
||||
[testenv:py39]
|
||||
basepython = python3.9
|
||||
setenv =
|
||||
TESTPATH=./notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/
|
||||
commands =
|
||||
|
Loading…
x
Reference in New Issue
Block a user