From d54873f8067250fbc4720d28134995b49c6b0f07 Mon Sep 17 00:00:00 2001 From: Cole Walker Date: Fri, 5 Aug 2022 15:38:40 -0400 Subject: [PATCH] [PTP] Support for overall sync status tracking This commit adds support for tracking the overall time sync status of a node by looking at the aggregated state of monitored time components and reporting "Locked/Holdover/Freerun" accordingly. If all subcomponents are Locked then the overall state reports Locked. If a component is degraded, then this will cause the overall state to transition. This functionality allows a consumer to subscribe to a single status rather than having to handle a subscription for each individual component. Also included in this change are some logic fixes for GNSS, PTP and OS Clock to handle cases where they were not transitioning properly. This change also replaces the "new_event" variable value with a bool instead of a string - the new_event var was not working properly previously and was causing the status to be published even if nothing had changed. This will reduce the number of updates being sent to clients. Story: 2010056 Task: 45963 Change-Id: I41fd2bf202df8a9b7d3d1266f50e41a71df78273 Signed-off-by: Cole Walker --- .../common/helpers/gnss_monitor.py | 53 ++- .../common/helpers/log_helper.py | 37 +- .../common/helpers/os_clock_monitor.py | 35 +- .../common/helpers/ptpsync.py | 20 +- .../model/dto/overallclockstate.py | 15 + .../trackingfunctionsdk/services/daemon.py | 317 +++++++++++++++--- .../tests/test_os_clock_monitor.py | 6 +- .../scripts/init/ptptracking_start.sh | 47 +-- 8 files changed, 422 insertions(+), 108 deletions(-) create mode 100644 notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/overallclockstate.py diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/gnss_monitor.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/gnss_monitor.py index 1fb937c..f0c3898 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/gnss_monitor.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/gnss_monitor.py @@ -4,11 +4,14 @@ # SPDX-License-Identifier: Apache-2.0 # import logging +import datetime + from abc import ABC, abstractmethod -from datetime import datetime from trackingfunctionsdk.common.helpers import log_helper +from trackingfunctionsdk.common.helpers import constants from trackingfunctionsdk.common.helpers.cgu_handler import CguHandler +from trackingfunctionsdk.model.dto.gnssstate import GnssState LOG = logging.getLogger(__name__) log_helper.config_logger(LOG) @@ -26,7 +29,7 @@ class Observer(ABC): class GnssMonitor(Observer): gnss_eec_state = "" gnss_pps_state = "" - event_time = None + _state = GnssState() gnss_cgu_handler = None def __init__(self, config_file, nmea_serialport=None, pci_addr=None, cgu_path=None): @@ -45,7 +48,8 @@ class GnssMonitor(Observer): self.gnss_cgu_handler.read_cgu() self.gnss_cgu_handler.cgu_output_to_dict() - self.dmesg_values_to_check = {'pin': 'GNSS-1PPS', 'pci_addr': self.gnss_cgu_handler.pci_addr} + self.dmesg_values_to_check = {'pin': 'GNSS-1PPS', + 'pci_addr': self.gnss_cgu_handler.pci_addr} # Initialize status if self.gnss_cgu_handler.cgu_output_parsed['EEC DPLL']['Current reference'] == 'GNSS-1PPS': @@ -54,23 +58,50 @@ class GnssMonitor(Observer): if self.gnss_cgu_handler.cgu_output_parsed['PPS DPLL']['Current reference'] == 'GNSS-1PPS': self.gnss_pps_state = self.gnss_cgu_handler.cgu_output_parsed['PPS DPLL']['Status'] - self.event_time = datetime.now().timestamp() - def update(self, subject, matched_line) -> None: LOG.info("Kernel event detected. %s" % matched_line) LOG.debug("GnssMonitor handler logic would run now") self.set_gnss_status() def set_gnss_status(self): - self.event_time = datetime.now().timestamp() self.gnss_cgu_handler.read_cgu() self.gnss_cgu_handler.cgu_output_to_dict() - self.gnss_eec_state = self.gnss_cgu_handler.cgu_output_parsed['EEC DPLL']['Status'] + self.gnss_eec_state = self.gnss_eec_state = \ + self.gnss_cgu_handler.cgu_output_parsed['EEC DPLL']['Status'] self.gnss_pps_state = self.gnss_cgu_handler.cgu_output_parsed['PPS DPLL']['Status'] LOG.debug("GNSS EEC Status is: %s" % self.gnss_eec_state) LOG.debug("GNSS PPS Status is: %s" % self.gnss_pps_state) + if self.gnss_pps_state == 'locked_ho_ack' and self.gnss_eec_state == 'locked_ho_ack': + self._state = GnssState.Locked + else: + self._state = GnssState.Freerun + + LOG.debug("Set state GNSS to %s" % self._state) + + def get_gnss_status(self, holdover_time, freq, sync_state, event_time): + current_time = datetime.datetime.utcnow().timestamp() + time_in_holdover = round(current_time - event_time) + previous_sync_state = sync_state + max_holdover_time = (holdover_time - freq * 2) + + self.set_gnss_status() + + if self._state == constants.FREERUN_PHC_STATE: + if previous_sync_state in [constants.UNKNOWN_PHC_STATE, constants.FREERUN_PHC_STATE]: + self._state = constants.FREERUN_PHC_STATE + elif previous_sync_state == constants.LOCKED_PHC_STATE: + self._state = constants.HOLDOVER_PHC_STATE + elif previous_sync_state == constants.HOLDOVER_PHC_STATE and \ + time_in_holdover < max_holdover_time: + self._state = constants.HOLDOVER_PHC_STATE + else: + self._state = constants.FREERUN_PHC_STATE + + # determine if os clock sync state has changed since the last check + if self._state != previous_sync_state: + new_event = True + event_time = datetime.datetime.utcnow().timestamp() + else: + new_event = False + return new_event, self._state, event_time - def __publish_gnss_status(self, force=False): - LOG.debug("Publish GNSS status.") - # TODO implement a publisher class to handle this - pass diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py index b0da27a..3f2c6b7 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py @@ -1,18 +1,19 @@ -# -# Copyright (c) 2021 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -import logging - -def get_logger(module_name): - logger = logging.getLogger(module_name) - return config_logger(logger) - -def config_logger(logger): - ''' - configure the logger: uncomment following lines for debugging - ''' - # logger.setLevel(level=logging.DEBUG) - return logger +import logging +import sys +# +# Copyright (c) 2021 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +def get_logger(module_name): + logger = logging.getLogger(module_name) + return config_logger(logger) + +def config_logger(logger): + ''' + configure the logger: uncomment following lines for debugging + ''' + logging.basicConfig(stream=sys.stdout) + logger.setLevel(level=logging.DEBUG) + return logger diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/os_clock_monitor.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/os_clock_monitor.py index 9b7b7cb..eea583b 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/os_clock_monitor.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/os_clock_monitor.py @@ -27,8 +27,8 @@ class OsClockMonitor: ptp_device = None offset = None - def __init__(self, init=True): - self.phc2sys_config = os.environ.get("PHC2SYS_CONFIG", constants.PHC2SYS_DEFAULT_CONFIG) + def __init__(self, init=True, phc2sys_config=constants.PHC2SYS_DEFAULT_CONFIG): + self.phc2sys_config = phc2sys_config self.set_phc2sys_instance() """Normally initialize all fields, but allow these to be skipped to assist with unit testing @@ -124,8 +124,7 @@ class OsClockMonitor: if offset_int > constants.PHC2SYS_TOLERANCE_HIGH or \ offset_int < constants.PHC2SYS_TOLERANCE_LOW: LOG.warning("PHC2SYS offset is outside of tolerance, handling state change.") - # TODO Implement handler for os clock state change - pass + self._state = OsClockState.Freerun else: LOG.info("PHC2SYS offset is within tolerance, OS clock state is LOCKED") self._state = OsClockState.Locked @@ -133,6 +132,34 @@ class OsClockMonitor: def get_os_clock_state(self): return self._state + def os_clock_status(self, holdover_time, freq, sync_state, event_time): + current_time = datetime.datetime.utcnow().timestamp() + time_in_holdover = round(current_time - event_time) + previous_sync_state = sync_state + max_holdover_time = (holdover_time - freq * 2) + + self.get_os_clock_offset() + self.set_os_clock_state() + + if self.get_os_clock_state() == constants.FREERUN_PHC_STATE: + if previous_sync_state in [constants.UNKNOWN_PHC_STATE, constants.FREERUN_PHC_STATE]: + self._state = constants.FREERUN_PHC_STATE + elif previous_sync_state == constants.LOCKED_PHC_STATE: + self._state = constants.HOLDOVER_PHC_STATE + elif previous_sync_state == constants.HOLDOVER_PHC_STATE and \ + time_in_holdover < max_holdover_time: + self._state = constants.HOLDOVER_PHC_STATE + else: + self._state = constants.FREERUN_PHC_STATE + + # determine if os clock sync state has changed since the last check + if self._state != previous_sync_state: + new_event = True + event_time = datetime.datetime.utcnow().timestamp() + else: + new_event = False + return new_event, self.get_os_clock_state(), event_time + if __name__ == "__main__": # This file can be run in a ptp-notification pod to verify the functionality of diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py index 516c5ea..d366bdf 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py @@ -20,8 +20,10 @@ import subprocess import datetime import logging from trackingfunctionsdk.common.helpers import constants +from trackingfunctionsdk.common.helpers import log_helper LOG = logging.getLogger(__name__) +log_helper.config_logger(LOG) # dictionary includes PMC commands used and keywords of intrest ptp_oper_dict = { @@ -166,7 +168,7 @@ def ptp_status(holdover_time, freq, sync_state, event_time): # Holdover —> Locked # # Freerun —> Locked # #################################### - current_time = datetime.datetime.now().timestamp() + current_time = datetime.datetime.utcnow().timestamp() time_in_holdover = round(current_time - event_time) previous_sync_state = sync_state # max holdover time is calculated to be in a 'safety' zoon @@ -179,25 +181,28 @@ def ptp_status(holdover_time, freq, sync_state, event_time): sync_state = check_results(result, total_ptp_keywords, port_count) else: sync_state = constants.FREERUN_PHC_STATE - # determine if transition into holdover mode (cannot be in holdover if system clock is not in sync) + # determine if transition into holdover mode (cannot be in holdover if system clock is not in + # sync) if sync_state == constants.FREERUN_PHC_STATE and phc2sys: if previous_sync_state in [constants.UNKNOWN_PHC_STATE, constants.FREERUN_PHC_STATE]: sync_state = constants.FREERUN_PHC_STATE elif previous_sync_state == constants.LOCKED_PHC_STATE: sync_state = constants.HOLDOVER_PHC_STATE - elif previous_sync_state == constants.HOLDOVER_PHC_STATE and time_in_holdover < max_holdover_time: + elif previous_sync_state == constants.HOLDOVER_PHC_STATE and time_in_holdover < \ + max_holdover_time: sync_state = constants.HOLDOVER_PHC_STATE else: - sync_state == constants.FREERUN_PHC_STATE + sync_state = constants.FREERUN_PHC_STATE # determine if ptp sync state has changed since the last one if sync_state != previous_sync_state: - new_event = "true" - event_time = datetime.datetime.now().timestamp() + new_event = True + event_time = datetime.datetime.utcnow().timestamp() else: - new_event = "false" + new_event = False return new_event, sync_state, event_time + def parse_resource_address(resource_address): # The format of resource address is: # /{clusterName}/{siteName}(/optional/hierarchy/..)/{nodeName}/{resource} @@ -207,6 +212,7 @@ def parse_resource_address(resource_address): resource_path = '/' + re.split('[/]', resource_address, 3)[3] return clusterName, nodeName, resource_path + def format_resource_address(node_name, resource): # Return a resource_address resource_address = '/./' + node_name + resource diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/overallclockstate.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/overallclockstate.py new file mode 100644 index 0000000..c4ee0ef --- /dev/null +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/overallclockstate.py @@ -0,0 +1,15 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from wsme import types as wtypes + +EnumOverallClockState = wtypes.Enum(str, 'Locked', 'Freerun', 'Holdover') + + +class OverallClockState(object): + Locked = "Locked" + Freerun = "Freerun" + Holdover = "Holdover" diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py index 2072a80..fd698e6 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py @@ -1,20 +1,17 @@ # -# Copyright (c) 2022 Wind River Systems, Inc. +# Copyright (c) 2021-2022 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # - -import os +import datetime import json -import time -import oslo_messaging -from oslo_config import cfg -from oslo_utils import uuidutils - import logging - import multiprocessing as mp +import os +import sys import threading +import time +from oslo_utils import uuidutils from trackingfunctionsdk.client.ptpeventproducer import PtpEventProducer from trackingfunctionsdk.common.helpers import constants @@ -22,22 +19,18 @@ from trackingfunctionsdk.common.helpers import ptpsync from trackingfunctionsdk.common.helpers import log_helper from trackingfunctionsdk.common.helpers.dmesg_watcher import DmesgWatcher from trackingfunctionsdk.common.helpers.gnss_monitor import GnssMonitor +from trackingfunctionsdk.common.helpers.os_clock_monitor import OsClockMonitor from trackingfunctionsdk.model.dto.ptpstate import PtpState +from trackingfunctionsdk.model.dto.gnssstate import GnssState +from trackingfunctionsdk.model.dto.osclockstate import OsClockState +from trackingfunctionsdk.model.dto.overallclockstate import OverallClockState from trackingfunctionsdk.model.dto.resourcetype import ResourceType from trackingfunctionsdk.model.dto.rpc_endpoint import RpcEndpointInfo -from trackingfunctionsdk.model.dto.resourcetype import ResourceType -from trackingfunctionsdk.model.dto.ptpstate import PtpState - -from trackingfunctionsdk.client.ptpeventproducer import PtpEventProducer - -from trackingfunctionsdk.common.helpers import ptpsync as ptpsync LOG = logging.getLogger(__name__) - -from trackingfunctionsdk.common.helpers import log_helper log_helper.config_logger(LOG) -THIS_NODE_NAME = os.environ.get("THIS_NODE_NAME",'controller-0') +THIS_NODE_NAME = os.environ.get("THIS_NODE_NAME", 'controller-0') # Event source to event type mapping source_type = { @@ -53,6 +46,8 @@ source_type = { } '''Entry point of Default Process Worker''' + + def ProcessWorkerDefault(event, sqlalchemy_conf_json, broker_transport_endpoint): worker = PtpWatcherDefault(event, sqlalchemy_conf_json, broker_transport_endpoint) worker.run() @@ -65,6 +60,21 @@ class PtpWatcherDefault: 'poll_freq_seconds': 2 } + DEFAULT_GNSSTRACKER_CONTEXT = { + 'holdover_seconds': 30, + 'poll_freq_seconds': 2 + } + + DEFAULT_OS_CLOCK_TRACKER_CONTEXT = { + 'holdover_seconds': 30, + 'poll_freq_seconds': 2 + } + + DEFAULT_OVERALL_SYNC_TRACKER_CONTEXT = { + 'holdover_seconds': 30, + 'poll_freq_seconds': 2 + } + class PtpRequestHandlerDefault(object): def __init__(self, watcher): self.watcher = watcher @@ -127,8 +137,26 @@ class PtpWatcherDefault: self.ptptracker_context['last_event_time'] = self.init_time self.ptptracker_context_lock = threading.Lock() + self.gnsstracker_context = self.daemon_context.get( + 'gnsstracker_context', PtpWatcherDefault.DEFAULT_GNSSTRACKER_CONTEXT) + self.gnsstracker_context['sync_state'] = GnssState.Freerun + self.gnsstracker_context['last_event_time'] = self.init_time + self.gnsstracker_context_lock = threading.Lock() + + self.osclocktracker_context = self.daemon_context.get( + 'os_clock_tracker_context', PtpWatcherDefault.DEFAULT_OS_CLOCK_TRACKER_CONTEXT) + self.osclocktracker_context['sync_state'] = OsClockState.Freerun + self.osclocktracker_context['last_event_time'] = self.init_time + self.osclocktracker_context_lock = threading.Lock() + + self.overalltracker_context = self.daemon_context.get( + 'overall_sync_tracker_context', PtpWatcherDefault.DEFAULT_OVERALL_SYNC_TRACKER_CONTEXT) + self.overalltracker_context['sync_state'] = OverallClockState.Freerun + self.overalltracker_context['last_event_time'] = self.init_time + self.overalltracker_context_lock = threading.Lock() + self.ptp_device_simulated = "true" == self.ptptracker_context.get('device_simulated', - "False").lower() + "False") self.event_timeout = float(self.ptptracker_context['poll_freq_seconds']) @@ -151,12 +179,15 @@ class PtpWatcherDefault: self.forced_publishing = False self.watcher = DmesgWatcher() - observer_list = [GnssMonitor(i) for i in self.daemon_context['GNSS_CONFIGS']] - for observer in observer_list: + self.observer_list = [GnssMonitor(i) for i in self.daemon_context['GNSS_CONFIGS']] + for observer in self.observer_list: self.watcher.attach(observer) self.watcher_thread = threading.Thread(target=self.watcher.run_watcher) + # Setup OS Clock monitor + self.os_clock_monitor = OsClockMonitor(phc2sys_config=self.daemon_context['PHC2SYS_CONFIG']) + def signal_ptp_event(self): if self.event: self.event.set() @@ -167,13 +198,18 @@ class PtpWatcherDefault: def run(self): # start location listener self.__start_listener() - # Start dmesg watcher + + # start GNSS monitoring self.watcher_thread.start() + while True: # announce the location forced = self.forced_publishing self.forced_publishing = False self.__publish_ptpstatus(forced) + self.__publish_os_clock_status(forced) + self.__publish_gnss_status(forced) + self.__publish_overall_sync_status(forced) if self.event.wait(self.event_timeout): LOG.debug("daemon control event is asserted") self.event.clear() @@ -184,12 +220,13 @@ class PtpWatcherDefault: self.__stop_listener() '''Start listener to answer querying from clients''' + def __start_listener(self): LOG.debug("start listener to answer location querying") self.ptpeventproducer.start_status_listener( self.__ptprequest_handler - ) + ) return def __stop_listener(self): @@ -198,6 +235,62 @@ class PtpWatcherDefault: self.ptpeventproducer.stop_status_listener(self.location_info) return + def __get_gnss_status(self, holdover_time, freq, sync_state, last_event_time, gnss_monitor): + new_event, sync_state, new_event_time = gnss_monitor.get_gnss_status( + holdover_time, freq, sync_state, last_event_time) + LOG.debug("Getting GNSS status.") + return new_event, sync_state, new_event_time + + def __get_os_clock_status(self, holdover_time, freq, sync_state, last_event_time): + new_event, sync_state, new_event_time = self.os_clock_monitor.os_clock_status( + holdover_time, freq, sync_state, last_event_time) + 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): + new_event = False + new_event_time = last_event_time + previous_sync_state = sync_state + current_time = datetime.datetime.utcnow().timestamp() + time_in_holdover = round(current_time - last_event_time) + max_holdover_time = (holdover_time - freq * 2) + gnss_state = None + os_clock_state = None + ptp_state = None + + LOG.debug("Getting overall sync state.") + for gnss in self.observer_list: + if gnss._state == GnssState.Holdover or gnss._state == GnssState.Freerun: + gnss_state = GnssState.Freerun + elif gnss._state == GnssState.Locked and gnss_state != GnssState.Freerun: + gnss_state = GnssState.Locked + + os_clock_state = self.os_clock_monitor.get_os_clock_state() + + ptp_state = self.ptptracker_context.get('sync_state') + + if gnss_state is GnssState.Freerun or os_clock_state is OsClockState.Freerun or ptp_state\ + is PtpState.Freerun: + sync_state = OverallClockState.Freerun + else: + sync_state = OverallClockState.Locked + + if sync_state == OverallClockState.Freerun: + if previous_sync_state in [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: + sync_state = OverallClockState.Holdover + else: + sync_state = OverallClockState.Freerun + + if sync_state != previous_sync_state: + new_event = True + new_event_time = datetime.datetime.utcnow().timestamp() + return new_event, sync_state, new_event_time + def __get_ptp_status(self, holdover_time, freq, sync_state, last_event_time): new_event = False new_event_time = last_event_time @@ -220,27 +313,27 @@ class PtpWatcherDefault: holdover_time, freq, sync_state, last_event_time) return new_event, sync_state, new_event_time - '''publish ptp status''' - def __publish_ptpstatus(self, forced=False): - holdover_time = float(self.ptptracker_context['holdover_seconds']) - freq = float(self.ptptracker_context['poll_freq_seconds']) - sync_state = self.ptptracker_context.get('sync_state', 'Unknown') - last_event_time = self.ptptracker_context.get('last_event_time', time.time()) + '''announce location''' - new_event, sync_state, new_event_time = self.__get_ptp_status( + def __publish_os_clock_status(self, forced=False): + holdover_time = float(self.osclocktracker_context['holdover_seconds']) + freq = float(self.osclocktracker_context['poll_freq_seconds']) + sync_state = self.osclocktracker_context.get('sync_state', 'Unknown') + last_event_time = self.osclocktracker_context.get('last_event_time', time.time()) + + new_event, sync_state, new_event_time = self.__get_os_clock_status( holdover_time, freq, sync_state, last_event_time) + LOG.debug("Got os clock status.") if new_event or forced: - # update context - self.ptptracker_context_lock.acquire() - self.ptptracker_context['sync_state'] = sync_state - self.ptptracker_context['last_event_time'] = new_event_time - self.ptptracker_context_lock.release() + self.osclocktracker_context_lock.acquire() + self.osclocktracker_context['sync_state'] = sync_state + self.osclocktracker_context['last_event_time'] = new_event_time + self.osclocktracker_context_lock.release() - # publish new event in API version v1 format - LOG.debug("publish ptp status to clients") + LOG.debug("Publish OS Clock Status") lastStatus = { - 'ResourceType': 'PTP', + 'ResourceType': 'OS Clock', 'EventData': { 'State': sync_state }, @@ -249,11 +342,50 @@ class PtpWatcherDefault: }, 'EventTimestamp': new_event_time } - self.ptpeventproducer.publish_status(lastStatus, 'PTP') - # publish new event in API version v2 format resource_address = ptpsync.format_resource_address( - self.node_name, constants.SOURCE_SYNC_SYNC_STATE) + self.node_name, constants.SOURCE_SYNC_OS_CLOCK) + lastStatus = { + 'id': uuidutils.generate_uuid(), + 'specversion': constants.SPEC_VERSION, + 'source': constants.SOURCE_SYNC_OS_CLOCK, + 'type': source_type[constants.SOURCE_SYNC_OS_CLOCK], + 'time': new_event_time, + 'data': { + 'version': constants.DATA_VERSION, + 'values': [ + { + 'data_type': constants.DATA_TYPE_NOTIFICATION, + 'ResourceAddress': resource_address, + 'value_type': constants.VALUE_TYPE_ENUMERATION, + 'value': sync_state + } + ] + } + } + self.ptpeventproducer.publish_status(lastStatus, constants.SOURCE_SYNC_OS_CLOCK) + self.ptpeventproducer.publish_status(lastStatus, constants.SOURCE_SYNC_ALL) + return + + def __publish_overall_sync_status(self, forced=False): + holdover_time = float(self.overalltracker_context['holdover_seconds']) + freq = float(self.overalltracker_context['poll_freq_seconds']) + sync_state = self.overalltracker_context.get('sync_state', 'Unknown') + last_event_time = self.overalltracker_context.get('last_event_time', time.time()) + + new_event, sync_state, new_event_time = self.__get_overall_sync_state( + holdover_time, freq, sync_state, last_event_time) + + if new_event or forced: + # Update context + self.overalltracker_context_lock.acquire() + self.overalltracker_context['sync_state'] = sync_state + self.overalltracker_context['last_event_time'] = new_event_time + self.overalltracker_context_lock.release() + + LOG.debug("Publish overall sync status.") + resource_address = ptpsync.format_resource_address( + self.node_name, constants.SOURCE_SYNC_SYNC_STATE) lastStatus = { 'id': uuidutils.generate_uuid(), 'specversion': constants.SPEC_VERSION, @@ -272,14 +404,115 @@ class PtpWatcherDefault: ] } } + self.ptpeventproducer.publish_status(lastStatus, constants.SOURCE_SYNC_SYNC_STATE) self.ptpeventproducer.publish_status(lastStatus, constants.SOURCE_SYNC_ALL) + + def __publish_gnss_status(self, forced=False): + holdover_time = float(self.gnsstracker_context['holdover_seconds']) + freq = float(self.gnsstracker_context['poll_freq_seconds']) + sync_state = self.gnsstracker_context.get('sync_state', 'Unknown') + last_event_time = self.gnsstracker_context.get('last_event_time', time.time()) + LOG.debug("GNSS sync_state %s" % sync_state) + + for gnss in self.observer_list: + new_event, sync_state, new_event_time = self.__get_gnss_status( + holdover_time, freq, sync_state, last_event_time, gnss) + + if new_event or forced: + # update context + self.gnsstracker_context_lock.acquire() + self.gnsstracker_context['sync_state'] = sync_state + self.gnsstracker_context['last_event_time'] = new_event_time + self.gnsstracker_context_lock.release() + + LOG.debug("Publish GNSS status.") + + # publish new event in API version v2 format + resource_address = ptpsync.format_resource_address( + self.node_name, constants.SOURCE_SYNC_GNSS_SYNC_STATUS) + lastStatus = { + 'id': uuidutils.generate_uuid(), + 'specversion': constants.SPEC_VERSION, + 'source': constants.SOURCE_SYNC_GNSS_SYNC_STATUS, + 'type': source_type[constants.SOURCE_SYNC_GNSS_SYNC_STATUS], + 'time': new_event_time, + 'data': { + 'version': constants.DATA_VERSION, + 'values': [ + { + 'data_type': constants.DATA_TYPE_NOTIFICATION, + 'ResourceAddress': resource_address, + 'value_type': constants.VALUE_TYPE_ENUMERATION, + 'value': sync_state + } + ] + } + } + self.ptpeventproducer.publish_status(lastStatus, + constants.SOURCE_SYNC_GNSS_SYNC_STATUS) + self.ptpeventproducer.publish_status(lastStatus, constants.SOURCE_SYNC_ALL) + return + + def __publish_ptpstatus(self, forced=False): + holdover_time = float(self.ptptracker_context['holdover_seconds']) + freq = float(self.ptptracker_context['poll_freq_seconds']) + sync_state = self.ptptracker_context.get('sync_state', 'Unknown') + last_event_time = self.ptptracker_context.get('last_event_time', time.time()) + + new_event, sync_state, new_event_time = self.__get_ptp_status( + holdover_time, freq, sync_state, last_event_time) + + if new_event or forced: + # update context + self.ptptracker_context_lock.acquire() + self.ptptracker_context['sync_state'] = sync_state + self.ptptracker_context['last_event_time'] = new_event_time + self.ptptracker_context_lock.release() + + # publish new event + LOG.debug("Publish ptp status to clients") + lastStatus = { + 'ResourceType': 'PTP', + 'EventData': { + 'State': sync_state + }, + 'ResourceQualifier': { + 'NodeName': self.node_name + }, + 'EventTimestamp': new_event_time + } + self.ptpeventproducer.publish_status(lastStatus, 'PTP') + + # publish new event in API version v2 format + resource_address = ptpsync.format_resource_address( + self.node_name, constants.SOURCE_SYNC_PTP_LOCK_STATE) + lastStatus = { + 'id': uuidutils.generate_uuid(), + 'specversion': constants.SPEC_VERSION, + 'source': constants.SOURCE_SYNC_PTP_LOCK_STATE, + 'type': source_type[constants.SOURCE_SYNC_PTP_LOCK_STATE], + 'time': new_event_time, + 'data': { + 'version': constants.DATA_VERSION, + 'values': [ + { + 'data_type': constants.DATA_TYPE_NOTIFICATION, + 'ResourceAddress': resource_address, + 'value_type': constants.VALUE_TYPE_ENUMERATION, + 'value': sync_state + } + ] + } + } + self.ptpeventproducer.publish_status(lastStatus, constants.SOURCE_SYNC_PTP_LOCK_STATE) + self.ptpeventproducer.publish_status(lastStatus, constants.SOURCE_SYNC_ALL) return class DaemonControl(object): - def __init__(self, sqlalchemy_conf_json, daemon_context_json, process_worker = None): + def __init__(self, sqlalchemy_conf_json, daemon_context_json, process_worker=None): self.event = mp.Event() self.daemon_context = json.loads(daemon_context_json) self.node_name = self.daemon_context['THIS_NODE_NAME'] diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_os_clock_monitor.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_os_clock_monitor.py index cf4aa6b..fddccc4 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_os_clock_monitor.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_os_clock_monitor.py @@ -14,13 +14,13 @@ from trackingfunctionsdk.common.helpers.os_clock_monitor import OsClockMonitor from trackingfunctionsdk.model.dto.osclockstate import OsClockState testpath = os.environ.get("TESTPATH", "") +phc2sys_test_config = constants.PTP_CONFIG_PATH + "phc2sys-phc2sys-test.conf" class OsClockMonitorTests(unittest.TestCase): - os.environ["PHC2SYS_CONFIG"] = constants.PTP_CONFIG_PATH + "phc2sys-phc2sys-test.conf" - clockmon = OsClockMonitor(init=False) + clockmon = OsClockMonitor(init=False, phc2sys_config=phc2sys_test_config) def test_set_phc2sys_instance(self): - self.clockmon = OsClockMonitor(init=False) + self.clockmon = OsClockMonitor(init=False, phc2sys_config=phc2sys_test_config) self.clockmon.set_phc2sys_instance() assert self.clockmon.phc2sys_instance == "phc2sys-test" diff --git a/stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification/resources/scripts/init/ptptracking_start.sh b/stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification/resources/scripts/init/ptptracking_start.sh index cfbeab2..5bf7836 100644 --- a/stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification/resources/scripts/init/ptptracking_start.sh +++ b/stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification/resources/scripts/init/ptptracking_start.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Wind River Systems, Inc. +# Copyright (c) 2021-2022 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -28,46 +28,47 @@ log_helper.config_logger(LOG) import os import json -import time # 引入time模块 -import oslo_messaging -from oslo_config import cfg from trackingfunctionsdk.services.daemon import DaemonControl -THIS_NAMESPACE = os.environ.get("THIS_NAMESPACE",'notification') -THIS_NODE_NAME = os.environ.get("THIS_NODE_NAME",'controller-1') -THIS_POD_IP = os.environ.get("THIS_POD_IP",'127.0.0.1') -REGISTRATION_USER = os.environ.get("REGISTRATION_USER", "admin") -REGISTRATION_PASS = os.environ.get("REGISTRATION_PASS", "admin") +THIS_NAMESPACE = os.environ.get("THIS_NAMESPACE", 'notification') +THIS_NODE_NAME = os.environ.get("THIS_NODE_NAME", 'controller-0') +THIS_POD_IP = os.environ.get("THIS_POD_IP", '127.0.0.1') +REGISTRATION_USER = os.environ.get("REGISTRATION_USER", "guest") +REGISTRATION_PASS = os.environ.get("REGISTRATION_PASS", "guest") REGISTRATION_PORT = os.environ.get("REGISTRATION_PORT", "5672") -REGISTRATION_HOST = os.environ.get("REGISTRATION_HOST",'registration.notification.svc.cluster.local') +# REGISTRATION_HOST = os.environ.get("REGISTRATION_HOST", 'registration.notification.svc.cluster.local') +REGISTRATION_HOST = os.environ.get("REGISTRATION_HOST", 'localhost') # 'rabbit://admin:admin@[127.0.0.1]:5672/' # 'rabbit://admin:admin@[::1]:5672/' REGISTRATION_TRANSPORT_ENDPOINT = 'rabbit://{0}:{1}@[{2}]:{3}'.format( - REGISTRATION_USER, REGISTRATION_PASS, REGISTRATION_HOST, REGISTRATION_PORT) + REGISTRATION_USER, REGISTRATION_PASS, REGISTRATION_HOST, REGISTRATION_PORT) -NOTIFICATION_BROKER_USER = os.environ.get("NOTIFICATIONSERVICE_USER", "admin") -NOTIFICATION_BROKER_PASS = os.environ.get("NOTIFICATIONSERVICE_PASS", "admin") +NOTIFICATION_BROKER_USER = os.environ.get("NOTIFICATIONSERVICE_USER", "guest") +NOTIFICATION_BROKER_PASS = os.environ.get("NOTIFICATIONSERVICE_PASS", "guest") NOTIFICATION_BROKER_PORT = os.environ.get("NOTIFICATIONSERVICE_PORT", "5672") NOTIFICATION_TRANSPORT_ENDPOINT = 'rabbit://{0}:{1}@[{2}]:{3}'.format( - NOTIFICATION_BROKER_USER, NOTIFICATION_BROKER_PASS, THIS_POD_IP, NOTIFICATION_BROKER_PORT) + NOTIFICATION_BROKER_USER, NOTIFICATION_BROKER_PASS, THIS_POD_IP, NOTIFICATION_BROKER_PORT) -PTP_DEVICE_SIMULATED = os.environ.get("PTP_DEVICE_SIMULATED", False) +PTP_DEVICE_SIMULATED = os.environ.get("PTP_DEVICE_SIMULATED", True) PTP_HOLDOVER_SECONDS = os.environ.get("PTP_HOLDOVER_SECONDS", 30) PTP_POLL_FREQ_SECONDS = os.environ.get("PTP_POLL_FREQ_SECONDS", 2) +GNSS_CONFIGS = os.environ.get("TS2PHC_CONFIGS", ["/ptp/ptpinstance/ts2phc-ts1.conf"]) +PHC2SYS_CONFIG = os.environ.get("PHC2SYS_CONFIG", "/ptp/ptpinstance/phc2sys-phc-inst1.conf") + context = { 'THIS_NAMESPACE': THIS_NAMESPACE, 'THIS_NODE_NAME': THIS_NODE_NAME, 'THIS_POD_IP': THIS_POD_IP, 'REGISTRATION_TRANSPORT_ENDPOINT': REGISTRATION_TRANSPORT_ENDPOINT, 'NOTIFICATION_TRANSPORT_ENDPOINT': NOTIFICATION_TRANSPORT_ENDPOINT, - # 'NOTIFICATION_BROKER_USER': NOTIFICATION_BROKER_USER, - # 'NOTIFICATION_BROKER_PASS': NOTIFICATION_BROKER_PASS, - # 'NOTIFICATION_BROKER_PORT': NOTIFICATION_BROKER_PORT + 'GNSS_CONFIGS': GNSS_CONFIGS, + 'PHC2SYS_CONFIG': PHC2SYS_CONFIG, + 'ptptracker_context': { 'device_simulated': PTP_DEVICE_SIMULATED, 'holdover_seconds': PTP_HOLDOVER_SECONDS, @@ -76,11 +77,11 @@ context = { } sqlalchemy_conf = { - 'url' : 'sqlite:///apiserver.db', - 'echo' : False, - 'echo_pool' : False, - 'pool_recycle' : 3600, - 'encoding' : 'utf-8' + 'url': 'sqlite:///apiserver.db', + 'echo': False, + 'echo_pool': False, + 'pool_recycle': 3600, + 'encoding': 'utf-8' } sqlalchemy_conf_json = json.dumps(sqlalchemy_conf)