[PTP] Add clockClass tracking and multi ptp instance support

Adds notification functionality for PTP clockClass data and re-works the
overall PTP sync status to allow for multiple ptp4l instances to be
tracked. Each ptp4l instance reports its own sync status and clockClass.

This change moves several static functions out of ptpsync.py and into
the PtpMonitor class. The remaining static functions have been moved
into utils.py

This change retains backwards compatibility allowing for v1 PTP
notifications and subscriptions to continue working while also providing
the v2 functionality required by the ORAN Notification standard.

This change also provides an override value for the logging level
exposed via a helm chart override.

Testing:
PASS: GET and Subscribe functions for each notification type
PASS: Multiple PTP instance support

Story: 2010056
Task: 46038
Task: 46039

Signed-off-by: Cole Walker <cole.walker@windriver.com>
Change-Id: Ic5456bc5a36f95186cdc6fe01ef1068b7124a5fc
This commit is contained in:
Cole Walker 2022-08-18 13:38:38 -04:00
parent 92ee78e86f
commit 939b72d8b1
10 changed files with 414 additions and 227 deletions
notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk
stx-ptp-notification-helm/stx-ptp-notification-helm/helm-charts/ptp-notification
resources/scripts/init
templates
values.yaml

@ -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"

@ -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

@ -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()

@ -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()))

@ -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}

@ -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

@ -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)

@ -21,16 +21,17 @@ cat <<EOF>/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,

@ -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

@ -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