diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/constants.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/constants.py index 67c8e70..68762de 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/constants.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/constants.py @@ -18,6 +18,7 @@ GM_CLOCK_CLASS = "gm.ClockClass" TIME_TRACEABLE = "timeTraceable" CLOCK_IDENTITY = "clockIdentity" GRANDMASTER_IDENTITY = "grandmasterIdentity" +CLOCK_CLASS = "clockClass" # expected values for valid ptp state SLAVE_MODE = "slave" TIME_IS_TRACEABLE1 = "1" 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 3f2c6b7..9b3d87d 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py @@ -1,19 +1,20 @@ -import logging -import sys # -# Copyright (c) 2021 Wind River Systems, Inc. +# Copyright (c) 2021-2022 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # +import logging +import sys +import os + + 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) + logger.setLevel(level=os.environ.get("LOGGING_LEVEL", "INFO")) 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 eea583b..169701a 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,7 +27,7 @@ class OsClockMonitor: ptp_device = None offset = None - def __init__(self, init=True, phc2sys_config=constants.PHC2SYS_DEFAULT_CONFIG): + def __init__(self, phc2sys_config, init=True): self.phc2sys_config = phc2sys_config self.set_phc2sys_instance() diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptp_monitor.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptp_monitor.py new file mode 100644 index 0000000..199be60 --- /dev/null +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptp_monitor.py @@ -0,0 +1,201 @@ +# +# Copyright (c) 2021-2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script provides the PTP synchronization status +# for PTP NIC configured as subordinate (slave mode) +# It relies on Linux ptp4l (PMC) module in order to work +# Sync status provided as: 'Locked', 'Holdover', 'Freerun' +# +# +import datetime +import logging +import os +import re +import sys + +from trackingfunctionsdk.model.dto.ptpstate import PtpState +from trackingfunctionsdk.common.helpers import constants +from trackingfunctionsdk.common.helpers import log_helper +from trackingfunctionsdk.common.helpers import ptpsync as utils + +LOG = logging.getLogger(__name__) +log_helper.config_logger(LOG) + + +class PtpMonitor: + _clock_class = None + _ptp_sync_state = PtpState.Freerun + _new_ptp_sync_event = False + _new_clock_class_event = False + _ptp_event_time = None + _clock_class_event_time = None + + # Critical resources + ptp4l_service_name = None + ptp4l_config = None + phc2sys_service_name = None + + ptp_oper_dict = { + # [pmc cmd, ptp keywords,...] + 1: ["'GET PORT_DATA_SET'", constants.PORT_STATE], + 2: ["'GET TIME_STATUS_NP'", constants.GM_PRESENT, constants.MASTER_OFFSET], + 3: ["'GET PARENT_DATA_SET'", constants.GM_CLOCK_CLASS, constants.GRANDMASTER_IDENTITY], + 4: ["'GET TIME_PROPERTIES_DATA_SET'", constants.TIME_TRACEABLE], + 5: ["'GET DEFAULT_DATA_SET'", constants.CLOCK_IDENTITY, constants.CLOCK_CLASS], + } + + pmc_query_results = {} + + def __init__(self, ptp4l_config, holdover_time, freq, init=True): + + if init: + self.ptp4l_config = ptp4l_config + pattern = '(?<=/ptp/ptpinstance/ptp4l-).*(?=.conf)' + match = re.search(pattern, self.ptp4l_config) + self.ptp4l_service_name = match.group() + LOG.debug(self.ptp4l_service_name) + self.phc2sys_service_name = os.environ.get('PHC2SYS_SERVICE_NAME', 'phc2sys') + self.holdover_time = int(holdover_time) + self.freq = int(freq) + 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_sync_state(self): + new_ptp_sync_event, ptp_sync_state, ptp_event_time = self.ptp_status() + if ptp_sync_state != self._ptp_sync_state: + self._new_ptp_sync_event = new_ptp_sync_event + self._ptp_sync_state = ptp_sync_state + self._ptp_event_time = ptp_event_time + else: + self._new_ptp_sync_event = new_ptp_sync_event + + def get_ptp_sync_state(self): + return self._ptp_sync_state + + def set_ptp_clock_class(self): + clock_class = self.pmc_query_results['clockClass'] + if clock_class != self._clock_class: + self._clock_class = clock_class + self._new_clock_class_event = True + self._clock_class_event_time = datetime.datetime.utcnow().timestamp() + LOG.debug(self.pmc_query_results) + LOG.info("PTP clock class is %s" % self._clock_class) + else: + self._new_clock_class_event = False + + def get_ptp_clock_class(self): + self.set_ptp_clock_class() + return self._new_clock_class_event, self._clock_class, self._clock_class_event_time + + def ptp_status(self): + # holdover_time - time phc can maintain clock + # freq - the frequency for monitoring the ptp status + # sync_state - the current ptp state + # event_time - the last time that ptp status was changed + #################################### + # event states: # + # Locked —> Holdover —> Freerun # + # Holdover —> Locked # + # Freerun —> Locked # + #################################### + current_time = datetime.datetime.utcnow().timestamp() + time_in_holdover = round(current_time - self._ptp_event_time) + previous_sync_state = self._ptp_sync_state + # max holdover time is calculated to be in a 'safety' zone + max_holdover_time = (self.holdover_time - self.freq * 2) + + pmc, ptp4l, phc2sys, ptp4lconf = utils.check_critical_resources(self.ptp4l_service_name, + self.phc2sys_service_name) + # run pmc command if preconditions met + if pmc and ptp4l and phc2sys and ptp4lconf: + self.pmc_query_results, total_ptp_keywords, port_count = self.ptpsync() + sync_state = utils.check_results(self.pmc_query_results, total_ptp_keywords, port_count) + else: + LOG.warning("Missing critical resource: PMC %s PTP4L %s PHC2SYS %s PTP4LCONF %s" % (pmc, ptp4l, phc2sys, ptp4lconf)) + sync_state = PtpState.Freerun + # determine if transition into holdover mode (cannot be in holdover if system clock is + # not in + # sync) + if sync_state == PtpState.Freerun and phc2sys: + if previous_sync_state in [constants.UNKNOWN_PHC_STATE, PtpState.Freerun]: + sync_state = PtpState.Freerun + elif previous_sync_state == PtpState.Locked: + sync_state = PtpState.Holdover + elif previous_sync_state == PtpState.Holdover and time_in_holdover < \ + max_holdover_time: + sync_state = PtpState.Holdover + else: + sync_state = PtpState.Freerun + + # determine if ptp sync state has changed since the last one + if sync_state != previous_sync_state: + new_event = True + self._ptp_event_time = datetime.datetime.utcnow().timestamp() + else: + new_event = False + return new_event, sync_state, self._ptp_event_time + + def ptpsync(self): + result = {} + total_ptp_keywords = 0 + port_count = 0 + + ptp_dict_to_use = self.ptp_oper_dict + len_dic = len(ptp_dict_to_use) + + for key in range(1, len_dic + 1): + cmd = ptp_dict_to_use[key][0] + cmd = "pmc -b 0 -u -f /ptp/ptpinstance/ptp4l-" + self.ptp4l_service_name + ".conf " +\ + cmd + + ptp_keyword = ptp_dict_to_use[key][1:] + total_ptp_keywords += len(ptp_keyword) + + out, err, errcode = utils.run_shell2('.', None, cmd) + if errcode != 0: + LOG.warning('pmc command returned unknown result') + sys.exit(0) + out = str(out) + try: + out = out.split("\\n\\t\\t") + except: + LOG.warning('cannot split "out" into a list') + sys.exit(0) + for state in out: + try: + state = state.split() + except: + LOG.warning('cannot split "state" into a list') + sys.exit(0) + if len(state) <= 1: + LOG.warning('not received the expected list length') + sys.exit(0) + for item in ptp_keyword: + if state[0] == item: + if item == constants.PORT_STATE: + port_count += 1 + result.update({constants.PORT.format(port_count): state[1]}) + else: + state[1] = state[1].replace('\\n', '') + state[1] = state[1].replace('\'', '') + result.update({state[0]: state[1]}) + # making sure at least one port is available + if port_count == 0: + port_count = 1 + # adding the possible ports minus one keyword not used, "portState" + total_ptp_keywords = total_ptp_keywords + port_count - 1 + return result, total_ptp_keywords, port_count + + +if __name__ == "__main__": + test_ptp = PtpMonitor() + LOG.debug("PTP sync state for %s is %s" % ( + test_ptp.ptp4l_service_name, test_ptp.get_ptp_sync_state())) + LOG.debug("PTP clock class for %s is %s" % ( + test_ptp.ptp4l_service_name, test_ptp.get_ptp_clock_class())) diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py index d366bdf..4a36f0c 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py @@ -25,19 +25,6 @@ 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 = { - # [pmc cmd, ptp keywords,...] - 1: ["'GET PORT_DATA_SET'", constants.PORT_STATE], - 2: ["'GET TIME_STATUS_NP'", constants.GM_PRESENT, constants.MASTER_OFFSET], - 3: ["'GET PARENT_DATA_SET'", constants.GM_CLOCK_CLASS, constants.GRANDMASTER_IDENTITY], - 4: ["'GET TIME_PROPERTIES_DATA_SET'", constants.TIME_TRACEABLE], - 5: ["'GET DEFAULT_DATA_SET'", constants.CLOCK_IDENTITY] -} - -ptp4l_service_name = os.environ.get('PTP4L_SERVICE_NAME', 'ptp4l') -phc2sys_service_name = os.environ.get('PHC2SYS_SERVICE_NAME', 'phc2sys') - # run subprocess and returns out, err, errcode def run_shell2(dir, ctx, args): @@ -54,7 +41,7 @@ def run_shell2(dir, ctx, args): return out, err, errcode -def check_critical_resources(): +def check_critical_resources(ptp4l_service_name, phc2sys_service_name): pmc = False ptp4l = False phc2sys = False @@ -89,6 +76,7 @@ def check_results(result, total_ptp_keywords, port_count): sync_state = constants.FREERUN_PHC_STATE else: local_gm = True + LOG.debug("Local node is a GM") for port in range(1, port_count + 1): if result[constants.PORT.format(port)].lower() == constants.SLAVE_MODE or local_gm: break @@ -105,104 +93,6 @@ def check_results(result, total_ptp_keywords, port_count): return sync_state -def ptpsync(): - result = {} - total_ptp_keywords = 0 - port_count = 0 - - ptp_dict_to_use = ptp_oper_dict - len_dic = len(ptp_dict_to_use) - - for key in range(1, len_dic + 1): - cmd = ptp_dict_to_use[key][0] - cmd = "pmc -b 0 -u -f /ptp/ptpinstance/ptp4l-" + ptp4l_service_name + ".conf " + cmd - - ptp_keyword = ptp_dict_to_use[key][1:] - total_ptp_keywords += len(ptp_keyword) - - out, err, errcode = run_shell2('.', None, cmd) - if errcode != 0: - LOG.warning('pmc command returned unknown result') - sys.exit(0) - out = str(out) - try: - out = out.split("\\n\\t\\t") - except: - LOG.warning('cannot split "out" into a list') - sys.exit(0) - for state in out: - try: - state = state.split() - except: - LOG.warning('cannot split "state" into a list') - sys.exit(0) - if len(state) <= 1: - LOG.warning('not received the expected list length') - sys.exit(0) - for item in ptp_keyword: - if state[0] == item: - if item == constants.PORT_STATE: - port_count += 1 - result.update({constants.PORT.format(port_count): state[1]}) - else: - state[1] = state[1].replace('\\n', '') - state[1] = state[1].replace('\'', '') - result.update({state[0]: state[1]}) - # making sure at least one port is available - if port_count == 0: - port_count = 1 - # adding the possible ports minus one keyword not used, "portState" - total_ptp_keywords = total_ptp_keywords + port_count - 1 - return result, total_ptp_keywords, port_count - - -def ptp_status(holdover_time, freq, sync_state, event_time): - result = {} - # holdover_time - time phc can maintain clock - # freq - the frequently for monitoring the ptp status - # sync_state - the current ptp state - # event_time - the last time that ptp status was changed - #################################### - # event states: # - # Locked —> Holdover —> Freerun # - # Holdover —> Locked # - # Freerun —> Locked # - #################################### - 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 - max_holdover_time = (holdover_time - freq * 2) - - pmc, ptp4l, phc2sys, ptp4lconf = check_critical_resources() - # run pmc command if preconditions met - if pmc and ptp4l and phc2sys and ptp4lconf: - result, total_ptp_keywords, port_count = ptpsync() - 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) - 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: - sync_state = constants.HOLDOVER_PHC_STATE - else: - 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.utcnow().timestamp() - else: - 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} diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py index c89b7c5..3183994 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py @@ -15,11 +15,12 @@ from oslo_utils import uuidutils from trackingfunctionsdk.client.ptpeventproducer import PtpEventProducer from trackingfunctionsdk.common.helpers import constants -from trackingfunctionsdk.common.helpers import ptpsync +from trackingfunctionsdk.common.helpers import ptpsync as utils 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.common.helpers.ptp_monitor import PtpMonitor from trackingfunctionsdk.model.dto.ptpstate import PtpState from trackingfunctionsdk.model.dto.gnssstate import GnssState from trackingfunctionsdk.model.dto.osclockstate import OsClockState @@ -80,8 +81,10 @@ class PtpWatcherDefault: self.watcher = watcher self.init_time = time.time() - def _build_event_response(self, resource_path, last_event_time, resource_address, sync_state): - if resource_path in [constants.SOURCE_SYNC_PTP_CLOCK_CLASS, constants.SOURCE_SYNCE_CLOCK_QUALITY]: + def _build_event_response(self, resource_path, last_event_time, resource_address, + sync_state): + if resource_path in [constants.SOURCE_SYNC_PTP_CLOCK_CLASS, + constants.SOURCE_SYNCE_CLOCK_QUALITY]: data_type = constants.DATA_TYPE_METRIC else: data_type = constants.DATA_TYPE_NOTIFICATION @@ -109,39 +112,51 @@ class PtpWatcherDefault: lastStatus = {} resource_address = rpc_kwargs.get('ResourceAddress', None) if resource_address: - _, nodename, resource_path = ptpsync.parse_resource_address(resource_address) + _, nodename, resource_path = utils.parse_resource_address(resource_address) if resource_path == constants.SOURCE_SYNC_ALL: resource_path = constants.SOURCE_SYNC_SYNC_STATE if resource_path == constants.SOURCE_SYNC_GNSS_SYNC_STATUS: self.watcher.gnsstracker_context_lock.acquire() - sync_state = self.watcher.gnsstracker_context.get('sync_state', GnssState.Freerun) - last_event_time = self.watcher.gnsstracker_context.get('last_event_time', time.time()) + sync_state = self.watcher.gnsstracker_context.get('sync_state', + GnssState.Freerun) + last_event_time = self.watcher.gnsstracker_context.get('last_event_time', + time.time()) self.watcher.gnsstracker_context_lock.release() - lastStatus = self._build_event_response(resource_path, last_event_time, resource_address, sync_state) + lastStatus = self._build_event_response(resource_path, last_event_time, + resource_address, sync_state) # elif resource_path == constants.SOURCE_SYNC_PTP_CLOCK_CLASS: elif resource_path == constants.SOURCE_SYNC_PTP_LOCK_STATE: self.watcher.ptptracker_context_lock.acquire() sync_state = self.watcher.ptptracker_context.get('sync_state', PtpState.Freerun) - last_event_time = self.watcher.ptptracker_context.get('last_event_time', time.time()) + last_event_time = self.watcher.ptptracker_context.get('last_event_time', + time.time()) self.watcher.ptptracker_context_lock.release() - lastStatus = self._build_event_response(resource_path, last_event_time, resource_address, sync_state) + lastStatus = self._build_event_response(resource_path, last_event_time, + resource_address, sync_state) elif resource_path == constants.SOURCE_SYNC_OS_CLOCK: self.watcher.osclocktracker_context_lock.acquire() - sync_state = self.watcher.osclocktracker_context.get('sync_state', OsClockState.Freerun) - last_event_time = self.watcher.osclocktracker_context.get('last_event_time', time.time()) + sync_state = self.watcher.osclocktracker_context.get('sync_state', + OsClockState.Freerun) + last_event_time = self.watcher.osclocktracker_context.get('last_event_time', + time.time()) self.watcher.osclocktracker_context_lock.release() - lastStatus = self._build_event_response(resource_path, last_event_time, resource_address, sync_state) + lastStatus = self._build_event_response(resource_path, last_event_time, + resource_address, sync_state) elif resource_path == constants.SOURCE_SYNC_SYNC_STATE: self.watcher.overalltracker_context_lock.acquire() - sync_state = self.watcher.overalltracker_context.get('sync_state', OverallClockState.Freerun) - last_event_time = self.watcher.overalltracker_context.get('last_event_time', time.time()) + sync_state = self.watcher.overalltracker_context.get('sync_state', + OverallClockState.Freerun) + last_event_time = self.watcher.overalltracker_context.get('last_event_time', + time.time()) self.watcher.overalltracker_context_lock.release() - lastStatus = self._build_event_response(resource_path, last_event_time, resource_address, sync_state) + lastStatus = self._build_event_response(resource_path, last_event_time, + resource_address, sync_state) LOG.debug("query_status: {}".format(lastStatus)) else: self.watcher.ptptracker_context_lock.acquire() sync_state = self.watcher.ptptracker_context.get('sync_state', PtpState.Freerun) - last_event_time = self.watcher.ptptracker_context.get('last_event_time', time.time()) + last_event_time = self.watcher.ptptracker_context.get('last_event_time', + time.time()) self.watcher.ptptracker_context_lock.release() lastStatus = { 'ResourceType': ResourceType.TypePTP, @@ -166,34 +181,47 @@ class PtpWatcherDefault: self.init_time = time.time() self.daemon_context = json.loads(daemon_context_json) - self.ptptracker_context = self.daemon_context.get( - 'ptptracker_context', PtpWatcherDefault.DEFAULT_PTPTRACKER_CONTEXT) - self.ptptracker_context['sync_state'] = PtpState.Freerun - 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() + # PTP Context + self.ptptracker_context = {} + for config in self.daemon_context['PTP4L_CONFIGS']: + self.ptptracker_context[config] = self.daemon_context.get( + 'ptptracker_context', PtpWatcherDefault.DEFAULT_PTPTRACKER_CONTEXT) + self.ptptracker_context[config]['sync_state'] = PtpState.Freerun + self.ptptracker_context[config]['last_event_time'] = self.init_time + self.ptp_device_simulated = "true" == self.ptptracker_context[config].get( + 'device_simulated', + "False") + self.ptptracker_context_lock = threading.Lock() + LOG.debug("ptptracker_context: %s" % self.ptptracker_context) + # GNSS Context + self.gnsstracker_context = {} + for config in self.daemon_context['GNSS_CONFIGS']: + self.gnsstracker_context[config] = self.daemon_context.get( + 'gnsstracker_context', PtpWatcherDefault.DEFAULT_GNSSTRACKER_CONTEXT) + self.gnsstracker_context[config]['sync_state'] = GnssState.Freerun + self.gnsstracker_context[config]['last_event_time'] = self.init_time + self.gnsstracker_context_lock = threading.Lock() + LOG.debug("gnsstracker_context: %s" % self.gnsstracker_context) + + # OS Clock Context + self.osclocktracker_context = {} 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() + # Overall Sync Context + self.overalltracker_context = {} 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") - - self.event_timeout = float(self.ptptracker_context['poll_freq_seconds']) + self.event_timeout = float(os.environ.get('CONTROL_TIMEOUT', 2)) self.node_name = self.daemon_context['THIS_NODE_NAME'] @@ -211,7 +239,10 @@ class PtpWatcherDefault: self.registration_broker_endpoint.TransportEndpoint) self.__ptprequest_handler = PtpWatcherDefault.PtpRequestHandlerDefault(self) - self.forced_publishing = False + + # Set forced_publishing to True so that initial states are published + # Main loop in run() sets it to false after the first iteration + self.forced_publishing = True self.watcher = DmesgWatcher() self.observer_list = [GnssMonitor(i) for i in self.daemon_context['GNSS_CONFIGS']] @@ -221,7 +252,13 @@ class PtpWatcherDefault: 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']) + self.os_clock_monitor = OsClockMonitor(phc2sys_config=self.daemon_context['PHC2SYS_CONFIG']) + + # Setup PTP Monitor(s) + self.ptp_monitor_list = [ + PtpMonitor(config, self.ptptracker_context[config]['holdover_seconds'], + self.ptptracker_context[config]['poll_freq_seconds']) for config in + self.daemon_context['PTP4L_CONFIGS']] def signal_ptp_event(self): if self.event: @@ -241,9 +278,11 @@ class PtpWatcherDefault: # announce the location forced = self.forced_publishing self.forced_publishing = False - self.__publish_ptpstatus(forced) + if self.ptptracker_context: + self.__publish_ptpstatus(forced) + if self.gnsstracker_context: + self.__publish_gnss_status(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") @@ -304,7 +343,7 @@ class PtpWatcherDefault: ptp_state = self.ptptracker_context.get('sync_state') - if gnss_state is GnssState.Freerun or os_clock_state is OsClockState.Freerun or ptp_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: @@ -326,7 +365,7 @@ class PtpWatcherDefault: 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): + def __get_ptp_status(self, holdover_time, freq, sync_state, last_event_time, ptp_monitor): new_event = False new_event_time = last_event_time if self.ptp_device_simulated: @@ -344,8 +383,7 @@ class PtpWatcherDefault: else: sync_state = PtpState.Freerun else: - new_event, sync_state, new_event_time = ptpsync.ptp_status( - holdover_time, freq, sync_state, last_event_time) + new_event, sync_state, new_event_time = ptp_monitor.ptp_status() return new_event, sync_state, new_event_time '''announce location''' @@ -378,7 +416,7 @@ class PtpWatcherDefault: 'EventTimestamp': new_event_time } # publish new event in API version v2 format - resource_address = ptpsync.format_resource_address( + resource_address = utils.format_resource_address( self.node_name, constants.SOURCE_SYNC_OS_CLOCK) lastStatus = { 'id': uuidutils.generate_uuid(), @@ -419,7 +457,7 @@ class PtpWatcherDefault: self.overalltracker_context_lock.release() LOG.debug("Publish overall sync status.") - resource_address = ptpsync.format_resource_address( + resource_address = utils.format_resource_address( self.node_name, constants.SOURCE_SYNC_SYNC_STATE) lastStatus = { 'id': uuidutils.generate_uuid(), @@ -444,27 +482,29 @@ class PtpWatcherDefault: 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: + holdover_time = float(self.gnsstracker_context[gnss.config_file]['holdover_seconds']) + freq = float(self.gnsstracker_context[gnss.config_file]['poll_freq_seconds']) + sync_state = self.gnsstracker_context[gnss.config_file].get('sync_state', 'Unknown') + last_event_time = self.gnsstracker_context[gnss.config_file].get('last_event_time', + time.time()) + new_event, sync_state, new_event_time = self.__get_gnss_status( holdover_time, freq, sync_state, last_event_time, gnss) + LOG.debug("GNSS sync_state %s" % sync_state) 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[gnss.config_file]['sync_state'] = sync_state + self.gnsstracker_context[gnss.config_file]['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( + resource_address = utils.format_resource_address( self.node_name, constants.SOURCE_SYNC_GNSS_SYNC_STATUS) lastStatus = { 'id': uuidutils.generate_uuid(), @@ -490,58 +530,105 @@ class PtpWatcherDefault: 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) + for ptp_monitor in self.ptp_monitor_list: + holdover_time = \ + float(self.ptptracker_context[ptp_monitor.ptp4l_config]['holdover_seconds']) + freq = float(self.ptptracker_context[ptp_monitor.ptp4l_config]['poll_freq_seconds']) + sync_state = self.ptptracker_context[ptp_monitor.ptp4l_config]. \ + get('sync_state', 'Unknown') + last_event_time = self.ptptracker_context[ptp_monitor.ptp4l_config] \ + .get('last_event_time', time.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() + new_event, sync_state, new_event_time = self.__get_ptp_status( + holdover_time, freq, sync_state, last_event_time, ptp_monitor) - # 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') + new_clock_class_event, clock_class, clock_class_event_time = \ + ptp_monitor.get_ptp_clock_class() - # 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 - } - ] + if new_event or forced: + # update context + self.ptptracker_context_lock.acquire() + self.ptptracker_context[ptp_monitor.ptp4l_config]['sync_state'] = sync_state + self.ptptracker_context[ptp_monitor.ptp4l_config][ + '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, constants.SOURCE_SYNC_PTP_LOCK_STATE) - self.ptpeventproducer.publish_status(lastStatus, constants.SOURCE_SYNC_ALL) + self.ptpeventproducer.publish_status(lastStatus, 'PTP') + + # publish new event in API version v2 format + resource_address = utils.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) + + if new_clock_class_event or forced: + # update context + self.ptptracker_context_lock.acquire() + self.ptptracker_context[ptp_monitor.ptp4l_config]['clock_class'] = clock_class + self.ptptracker_context[ptp_monitor.ptp4l_config]['last_clock_class_event_time'] \ + = clock_class_event_time + self.ptptracker_context_lock.release() + + resource_address = utils.format_resource_address( + self.node_name, constants.SOURCE_SYNC_PTP_CLOCK_CLASS) + + lastClockClassStatus = { + 'id': uuidutils.generate_uuid(), + 'specversion': constants.SPEC_VERSION, + 'source': constants.SOURCE_SYNC_PTP_CLOCK_CLASS, + 'type': source_type[constants.SOURCE_SYNC_PTP_CLOCK_CLASS], + 'time': clock_class_event_time, + 'data': { + 'version': constants.DATA_VERSION, + 'values': [ + { + 'data_type': constants.DATA_TYPE_NOTIFICATION, + 'ResourceAddress': resource_address, + 'value_type': constants.VALUE_TYPE_METRIC, + 'value': clock_class + } + ] + } + } + LOG.info("Publishing clockClass for %s: %s" % (ptp_monitor.ptp4l_service_name, + clock_class)) + self.ptpeventproducer.publish_status(lastClockClassStatus, + constants.SOURCE_SYNC_PTP_CLOCK_CLASS) + self.ptpeventproducer.publish_status(lastClockClassStatus, + constants.SOURCE_SYNC_ALL) + return 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 fddccc4..6682c58 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 @@ -17,15 +17,15 @@ testpath = os.environ.get("TESTPATH", "") phc2sys_test_config = constants.PTP_CONFIG_PATH + "phc2sys-phc2sys-test.conf" class OsClockMonitorTests(unittest.TestCase): - clockmon = OsClockMonitor(init=False, phc2sys_config=phc2sys_test_config) + clockmon = OsClockMonitor(phc2sys_config=phc2sys_test_config, init=False) def test_set_phc2sys_instance(self): - self.clockmon = OsClockMonitor(init=False, phc2sys_config=phc2sys_test_config) + self.clockmon = OsClockMonitor(phc2sys_config=phc2sys_test_config, init=False) self.clockmon.set_phc2sys_instance() assert self.clockmon.phc2sys_instance == "phc2sys-test" def test_check_config_file_interface(self): - self.clockmon = OsClockMonitor(init=False) + self.clockmon = OsClockMonitor(phc2sys_config=phc2sys_test_config, init=False) self.clockmon.phc2sys_config = testpath + "test_input_files/phc2sys-test.conf" self.assertEqual(self.clockmon._check_config_file_interface(), "ens2f0") @@ -56,7 +56,7 @@ class OsClockMonitorTests(unittest.TestCase): ]) def test_get_interface_phc_device(self, glob_patched): # Success path - self.clockmon = OsClockMonitor(init=False) + self.clockmon = OsClockMonitor(phc2sys_config=phc2sys_test_config, init=False) self.clockmon.phc_interface = "ens1f0" self.assertEqual(self.clockmon._get_interface_phc_device(), 'ptp0') @@ -69,13 +69,13 @@ class OsClockMonitorTests(unittest.TestCase): @mock.patch('trackingfunctionsdk.common.helpers.os_clock_monitor.subprocess.check_output', side_effect=[b'-37000000015ns']) def test_get_os_clock_offset(self, subprocess_patched): - self.clockmon = OsClockMonitor(init=False) + self.clockmon = OsClockMonitor(phc2sys_config=phc2sys_test_config, init=False) self.clockmon.ptp_device = 'ptp0' self.clockmon.get_os_clock_offset() assert self.clockmon.offset == '37000000015' def test_set_os_closck_state(self): - self.clockmon = OsClockMonitor(init=False) + self.clockmon = OsClockMonitor(phc2sys_config=phc2sys_test_config, init=False) self.clockmon.offset = '37000000015' self.clockmon.set_os_clock_state() self.assertEqual(self.clockmon.get_os_clock_state(), OsClockState.Locked) 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 c03613e..31f957a 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 @@ -21,16 +21,17 @@ cat </root/ptptracking-daemon.py #!/usr/bin/python3 # -*- coding: UTF-8 -*- import logging -LOG = logging.getLogger(__name__) - -from trackingfunctionsdk.common.helpers import log_helper -log_helper.config_logger(LOG) - import os import json +from trackingfunctionsdk.common.helpers import log_helper from trackingfunctionsdk.services.daemon import DaemonControl +LOG = logging.getLogger(__name__) +log_helper.config_logger(LOG) + + + 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') @@ -66,8 +67,10 @@ OS_CLOCK_POLL_FREQ_SECONDS = os.environ.get("OS_CLOCK_POLL_FREQ_SECONDS", 2) OVERALL_HOLDOVER_SECONDS = os.environ.get("OVERALL_HOLDOVER_SECONDS", 30) OVERALL_POLL_FREQ_SECONDS = os.environ.get("OVERALL_POLL_FREQ_SECONDS", 2) -GNSS_CONFIGS = json.loads(os.environ.get("TS2PHC_CONFIGS", ["/ptp/ptpinstance/ts2phc-ts1.conf"])) +GNSS_CONFIGS = json.loads(os.environ.get("TS2PHC_CONFIGS", '["/ptp/ptpinstance/ts2phc-tc1.conf"]')) PHC2SYS_CONFIG = os.environ.get("PHC2SYS_CONFIG", "/ptp/ptpinstance/phc2sys-phc-inst1.conf") +PTP4L_CONFIGS = json.loads(os.environ.get("PTP4L_CONFIGS", '["/ptp/ptpinstance/ptp4l-ptp-legacy.conf"]')) + context = { 'THIS_NAMESPACE': THIS_NAMESPACE, @@ -77,6 +80,7 @@ context = { 'NOTIFICATION_TRANSPORT_ENDPOINT': NOTIFICATION_TRANSPORT_ENDPOINT, 'GNSS_CONFIGS': GNSS_CONFIGS, 'PHC2SYS_CONFIG': PHC2SYS_CONFIG, + 'PTP4L_CONFIGS' : PTP4L_CONFIGS, 'ptptracker_context': { 'device_simulated': PTP_DEVICE_SIMULATED, diff --git a/stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification/templates/daemonset.yaml b/stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification/templates/daemonset.yaml index 8454ce6..c8337fe 100644 --- a/stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification/templates/daemonset.yaml +++ b/stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification/templates/daemonset.yaml @@ -147,6 +147,8 @@ spec: value: "{{ .Values.ptptracking.ts2phcServiceName }}" - name: TS2PHC_CONFIGS value: '["/ptp/ptpinstance/ts2phc-{{.Values.ptptracking.ts2phcServiceName}}.conf"]' + - name: LOGGING_LEVEL + value: "{{ .Values.ptptracking.log_level }}" command: ["/bin/bash", "/mnt/ptptracking_start.sh"] securityContext: privileged: true diff --git a/stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification/values.yaml b/stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification/values.yaml index 60df241..3870edf 100644 --- a/stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification/values.yaml +++ b/stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification/values.yaml @@ -70,6 +70,7 @@ ptptracking: ptp4lServiceName: ptp4l-legacy phc2sysServiceName: phc2sys-legacy ts2phcServiceName: ts2phc-legacy + log_level: INFO image: repository: starlingx/notificationservice-base tag: stx.7.0-v1.0.5