[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

View File

@ -18,6 +18,7 @@ GM_CLOCK_CLASS = "gm.ClockClass"
TIME_TRACEABLE = "timeTraceable" TIME_TRACEABLE = "timeTraceable"
CLOCK_IDENTITY = "clockIdentity" CLOCK_IDENTITY = "clockIdentity"
GRANDMASTER_IDENTITY = "grandmasterIdentity" GRANDMASTER_IDENTITY = "grandmasterIdentity"
CLOCK_CLASS = "clockClass"
# expected values for valid ptp state # expected values for valid ptp state
SLAVE_MODE = "slave" SLAVE_MODE = "slave"
TIME_IS_TRACEABLE1 = "1" TIME_IS_TRACEABLE1 = "1"

View File

@ -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 # SPDX-License-Identifier: Apache-2.0
# #
import logging
import sys
import os
def get_logger(module_name): def get_logger(module_name):
logger = logging.getLogger(module_name) logger = logging.getLogger(module_name)
return config_logger(logger) return config_logger(logger)
def config_logger(logger): def config_logger(logger):
'''
configure the logger: uncomment following lines for debugging
'''
logging.basicConfig(stream=sys.stdout) logging.basicConfig(stream=sys.stdout)
logger.setLevel(level=logging.DEBUG) logger.setLevel(level=os.environ.get("LOGGING_LEVEL", "INFO"))
return logger return logger

View File

@ -27,7 +27,7 @@ class OsClockMonitor:
ptp_device = None ptp_device = None
offset = 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.phc2sys_config = phc2sys_config
self.set_phc2sys_instance() self.set_phc2sys_instance()

View File

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

View File

@ -25,19 +25,6 @@ from trackingfunctionsdk.common.helpers import log_helper
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
log_helper.config_logger(LOG) 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 # run subprocess and returns out, err, errcode
def run_shell2(dir, ctx, args): def run_shell2(dir, ctx, args):
@ -54,7 +41,7 @@ def run_shell2(dir, ctx, args):
return out, err, errcode return out, err, errcode
def check_critical_resources(): def check_critical_resources(ptp4l_service_name, phc2sys_service_name):
pmc = False pmc = False
ptp4l = False ptp4l = False
phc2sys = False phc2sys = False
@ -89,6 +76,7 @@ def check_results(result, total_ptp_keywords, port_count):
sync_state = constants.FREERUN_PHC_STATE sync_state = constants.FREERUN_PHC_STATE
else: else:
local_gm = True local_gm = True
LOG.debug("Local node is a GM")
for port in range(1, port_count + 1): for port in range(1, port_count + 1):
if result[constants.PORT.format(port)].lower() == constants.SLAVE_MODE or local_gm: if result[constants.PORT.format(port)].lower() == constants.SLAVE_MODE or local_gm:
break break
@ -105,104 +93,6 @@ def check_results(result, total_ptp_keywords, port_count):
return sync_state 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): def parse_resource_address(resource_address):
# The format of resource address is: # The format of resource address is:
# /{clusterName}/{siteName}(/optional/hierarchy/..)/{nodeName}/{resource} # /{clusterName}/{siteName}(/optional/hierarchy/..)/{nodeName}/{resource}

View File

@ -15,11 +15,12 @@ from oslo_utils import uuidutils
from trackingfunctionsdk.client.ptpeventproducer import PtpEventProducer from trackingfunctionsdk.client.ptpeventproducer import PtpEventProducer
from trackingfunctionsdk.common.helpers import constants 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 import log_helper
from trackingfunctionsdk.common.helpers.dmesg_watcher import DmesgWatcher from trackingfunctionsdk.common.helpers.dmesg_watcher import DmesgWatcher
from trackingfunctionsdk.common.helpers.gnss_monitor import GnssMonitor from trackingfunctionsdk.common.helpers.gnss_monitor import GnssMonitor
from trackingfunctionsdk.common.helpers.os_clock_monitor import OsClockMonitor 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.ptpstate import PtpState
from trackingfunctionsdk.model.dto.gnssstate import GnssState from trackingfunctionsdk.model.dto.gnssstate import GnssState
from trackingfunctionsdk.model.dto.osclockstate import OsClockState from trackingfunctionsdk.model.dto.osclockstate import OsClockState
@ -80,8 +81,10 @@ class PtpWatcherDefault:
self.watcher = watcher self.watcher = watcher
self.init_time = time.time() self.init_time = time.time()
def _build_event_response(self, resource_path, last_event_time, resource_address, sync_state): def _build_event_response(self, resource_path, last_event_time, resource_address,
if resource_path in [constants.SOURCE_SYNC_PTP_CLOCK_CLASS, constants.SOURCE_SYNCE_CLOCK_QUALITY]: sync_state):
if resource_path in [constants.SOURCE_SYNC_PTP_CLOCK_CLASS,
constants.SOURCE_SYNCE_CLOCK_QUALITY]:
data_type = constants.DATA_TYPE_METRIC data_type = constants.DATA_TYPE_METRIC
else: else:
data_type = constants.DATA_TYPE_NOTIFICATION data_type = constants.DATA_TYPE_NOTIFICATION
@ -109,39 +112,51 @@ class PtpWatcherDefault:
lastStatus = {} lastStatus = {}
resource_address = rpc_kwargs.get('ResourceAddress', None) resource_address = rpc_kwargs.get('ResourceAddress', None)
if resource_address: 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: if resource_path == constants.SOURCE_SYNC_ALL:
resource_path = constants.SOURCE_SYNC_SYNC_STATE resource_path = constants.SOURCE_SYNC_SYNC_STATE
if resource_path == constants.SOURCE_SYNC_GNSS_SYNC_STATUS: if resource_path == constants.SOURCE_SYNC_GNSS_SYNC_STATUS:
self.watcher.gnsstracker_context_lock.acquire() self.watcher.gnsstracker_context_lock.acquire()
sync_state = self.watcher.gnsstracker_context.get('sync_state', GnssState.Freerun) sync_state = self.watcher.gnsstracker_context.get('sync_state',
last_event_time = self.watcher.gnsstracker_context.get('last_event_time', time.time()) GnssState.Freerun)
last_event_time = self.watcher.gnsstracker_context.get('last_event_time',
time.time())
self.watcher.gnsstracker_context_lock.release() 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_CLOCK_CLASS:
elif resource_path == constants.SOURCE_SYNC_PTP_LOCK_STATE: elif resource_path == constants.SOURCE_SYNC_PTP_LOCK_STATE:
self.watcher.ptptracker_context_lock.acquire() self.watcher.ptptracker_context_lock.acquire()
sync_state = self.watcher.ptptracker_context.get('sync_state', PtpState.Freerun) 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() 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: elif resource_path == constants.SOURCE_SYNC_OS_CLOCK:
self.watcher.osclocktracker_context_lock.acquire() self.watcher.osclocktracker_context_lock.acquire()
sync_state = self.watcher.osclocktracker_context.get('sync_state', OsClockState.Freerun) sync_state = self.watcher.osclocktracker_context.get('sync_state',
last_event_time = self.watcher.osclocktracker_context.get('last_event_time', time.time()) OsClockState.Freerun)
last_event_time = self.watcher.osclocktracker_context.get('last_event_time',
time.time())
self.watcher.osclocktracker_context_lock.release() 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: elif resource_path == constants.SOURCE_SYNC_SYNC_STATE:
self.watcher.overalltracker_context_lock.acquire() self.watcher.overalltracker_context_lock.acquire()
sync_state = self.watcher.overalltracker_context.get('sync_state', OverallClockState.Freerun) sync_state = self.watcher.overalltracker_context.get('sync_state',
last_event_time = self.watcher.overalltracker_context.get('last_event_time', time.time()) OverallClockState.Freerun)
last_event_time = self.watcher.overalltracker_context.get('last_event_time',
time.time())
self.watcher.overalltracker_context_lock.release() 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)) LOG.debug("query_status: {}".format(lastStatus))
else: else:
self.watcher.ptptracker_context_lock.acquire() self.watcher.ptptracker_context_lock.acquire()
sync_state = self.watcher.ptptracker_context.get('sync_state', PtpState.Freerun) 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() self.watcher.ptptracker_context_lock.release()
lastStatus = { lastStatus = {
'ResourceType': ResourceType.TypePTP, 'ResourceType': ResourceType.TypePTP,
@ -166,34 +181,47 @@ class PtpWatcherDefault:
self.init_time = time.time() self.init_time = time.time()
self.daemon_context = json.loads(daemon_context_json) self.daemon_context = json.loads(daemon_context_json)
self.ptptracker_context = self.daemon_context.get(
# 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) 'ptptracker_context', PtpWatcherDefault.DEFAULT_PTPTRACKER_CONTEXT)
self.ptptracker_context['sync_state'] = PtpState.Freerun self.ptptracker_context[config]['sync_state'] = PtpState.Freerun
self.ptptracker_context['last_event_time'] = self.init_time 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() self.ptptracker_context_lock = threading.Lock()
LOG.debug("ptptracker_context: %s" % self.ptptracker_context)
self.gnsstracker_context = self.daemon_context.get( # 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) 'gnsstracker_context', PtpWatcherDefault.DEFAULT_GNSSTRACKER_CONTEXT)
self.gnsstracker_context['sync_state'] = GnssState.Freerun self.gnsstracker_context[config]['sync_state'] = GnssState.Freerun
self.gnsstracker_context['last_event_time'] = self.init_time self.gnsstracker_context[config]['last_event_time'] = self.init_time
self.gnsstracker_context_lock = threading.Lock() 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( self.osclocktracker_context = self.daemon_context.get(
'os_clock_tracker_context', PtpWatcherDefault.DEFAULT_OS_CLOCK_TRACKER_CONTEXT) 'os_clock_tracker_context', PtpWatcherDefault.DEFAULT_OS_CLOCK_TRACKER_CONTEXT)
self.osclocktracker_context['sync_state'] = OsClockState.Freerun self.osclocktracker_context['sync_state'] = OsClockState.Freerun
self.osclocktracker_context['last_event_time'] = self.init_time self.osclocktracker_context['last_event_time'] = self.init_time
self.osclocktracker_context_lock = threading.Lock() self.osclocktracker_context_lock = threading.Lock()
# Overall Sync Context
self.overalltracker_context = {}
self.overalltracker_context = self.daemon_context.get( self.overalltracker_context = self.daemon_context.get(
'overall_sync_tracker_context', PtpWatcherDefault.DEFAULT_OVERALL_SYNC_TRACKER_CONTEXT) 'overall_sync_tracker_context', PtpWatcherDefault.DEFAULT_OVERALL_SYNC_TRACKER_CONTEXT)
self.overalltracker_context['sync_state'] = OverallClockState.Freerun self.overalltracker_context['sync_state'] = OverallClockState.Freerun
self.overalltracker_context['last_event_time'] = self.init_time self.overalltracker_context['last_event_time'] = self.init_time
self.overalltracker_context_lock = threading.Lock() self.overalltracker_context_lock = threading.Lock()
self.ptp_device_simulated = "true" == self.ptptracker_context.get('device_simulated', self.event_timeout = float(os.environ.get('CONTROL_TIMEOUT', 2))
"False")
self.event_timeout = float(self.ptptracker_context['poll_freq_seconds'])
self.node_name = self.daemon_context['THIS_NODE_NAME'] self.node_name = self.daemon_context['THIS_NODE_NAME']
@ -211,7 +239,10 @@ class PtpWatcherDefault:
self.registration_broker_endpoint.TransportEndpoint) self.registration_broker_endpoint.TransportEndpoint)
self.__ptprequest_handler = PtpWatcherDefault.PtpRequestHandlerDefault(self) 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.watcher = DmesgWatcher()
self.observer_list = [GnssMonitor(i) for i in self.daemon_context['GNSS_CONFIGS']] self.observer_list = [GnssMonitor(i) for i in self.daemon_context['GNSS_CONFIGS']]
@ -223,6 +254,12 @@ class PtpWatcherDefault:
# Setup OS Clock monitor # 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): def signal_ptp_event(self):
if self.event: if self.event:
self.event.set() self.event.set()
@ -241,9 +278,11 @@ class PtpWatcherDefault:
# announce the location # announce the location
forced = self.forced_publishing forced = self.forced_publishing
self.forced_publishing = False self.forced_publishing = False
if self.ptptracker_context:
self.__publish_ptpstatus(forced) self.__publish_ptpstatus(forced)
self.__publish_os_clock_status(forced) if self.gnsstracker_context:
self.__publish_gnss_status(forced) self.__publish_gnss_status(forced)
self.__publish_os_clock_status(forced)
self.__publish_overall_sync_status(forced) self.__publish_overall_sync_status(forced)
if self.event.wait(self.event_timeout): if self.event.wait(self.event_timeout):
LOG.debug("daemon control event is asserted") LOG.debug("daemon control event is asserted")
@ -326,7 +365,7 @@ class PtpWatcherDefault:
new_event_time = datetime.datetime.utcnow().timestamp() new_event_time = datetime.datetime.utcnow().timestamp()
return new_event, sync_state, new_event_time 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 = False
new_event_time = last_event_time new_event_time = last_event_time
if self.ptp_device_simulated: if self.ptp_device_simulated:
@ -344,8 +383,7 @@ class PtpWatcherDefault:
else: else:
sync_state = PtpState.Freerun sync_state = PtpState.Freerun
else: else:
new_event, sync_state, new_event_time = ptpsync.ptp_status( new_event, sync_state, new_event_time = ptp_monitor.ptp_status()
holdover_time, freq, sync_state, last_event_time)
return new_event, sync_state, new_event_time return new_event, sync_state, new_event_time
'''announce location''' '''announce location'''
@ -378,7 +416,7 @@ class PtpWatcherDefault:
'EventTimestamp': new_event_time 'EventTimestamp': new_event_time
} }
# publish new event in API version v2 format # 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) self.node_name, constants.SOURCE_SYNC_OS_CLOCK)
lastStatus = { lastStatus = {
'id': uuidutils.generate_uuid(), 'id': uuidutils.generate_uuid(),
@ -419,7 +457,7 @@ class PtpWatcherDefault:
self.overalltracker_context_lock.release() self.overalltracker_context_lock.release()
LOG.debug("Publish overall sync status.") 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) self.node_name, constants.SOURCE_SYNC_SYNC_STATE)
lastStatus = { lastStatus = {
'id': uuidutils.generate_uuid(), 'id': uuidutils.generate_uuid(),
@ -444,27 +482,29 @@ class PtpWatcherDefault:
self.ptpeventproducer.publish_status(lastStatus, constants.SOURCE_SYNC_ALL) self.ptpeventproducer.publish_status(lastStatus, constants.SOURCE_SYNC_ALL)
def __publish_gnss_status(self, forced=False): 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: 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( new_event, sync_state, new_event_time = self.__get_gnss_status(
holdover_time, freq, sync_state, last_event_time, gnss) holdover_time, freq, sync_state, last_event_time, gnss)
LOG.debug("GNSS sync_state %s" % sync_state)
if new_event or forced: if new_event or forced:
# update context # update context
self.gnsstracker_context_lock.acquire() self.gnsstracker_context_lock.acquire()
self.gnsstracker_context['sync_state'] = sync_state self.gnsstracker_context[gnss.config_file]['sync_state'] = sync_state
self.gnsstracker_context['last_event_time'] = new_event_time self.gnsstracker_context[gnss.config_file]['last_event_time'] = new_event_time
self.gnsstracker_context_lock.release() self.gnsstracker_context_lock.release()
LOG.debug("Publish GNSS status.") LOG.debug("Publish GNSS status.")
# publish new event in API version v2 format # 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) self.node_name, constants.SOURCE_SYNC_GNSS_SYNC_STATUS)
lastStatus = { lastStatus = {
'id': uuidutils.generate_uuid(), 'id': uuidutils.generate_uuid(),
@ -490,19 +530,28 @@ class PtpWatcherDefault:
return return
def __publish_ptpstatus(self, forced=False): def __publish_ptpstatus(self, forced=False):
holdover_time = float(self.ptptracker_context['holdover_seconds'])
freq = float(self.ptptracker_context['poll_freq_seconds']) for ptp_monitor in self.ptp_monitor_list:
sync_state = self.ptptracker_context.get('sync_state', 'Unknown') holdover_time = \
last_event_time = self.ptptracker_context.get('last_event_time', time.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())
new_event, sync_state, new_event_time = self.__get_ptp_status( new_event, sync_state, new_event_time = self.__get_ptp_status(
holdover_time, freq, sync_state, last_event_time) holdover_time, freq, sync_state, last_event_time, ptp_monitor)
new_clock_class_event, clock_class, clock_class_event_time = \
ptp_monitor.get_ptp_clock_class()
if new_event or forced: if new_event or forced:
# update context # update context
self.ptptracker_context_lock.acquire() self.ptptracker_context_lock.acquire()
self.ptptracker_context['sync_state'] = sync_state self.ptptracker_context[ptp_monitor.ptp4l_config]['sync_state'] = sync_state
self.ptptracker_context['last_event_time'] = new_event_time self.ptptracker_context[ptp_monitor.ptp4l_config][
'last_event_time'] = new_event_time
self.ptptracker_context_lock.release() self.ptptracker_context_lock.release()
# publish new event # publish new event
@ -520,7 +569,7 @@ class PtpWatcherDefault:
self.ptpeventproducer.publish_status(lastStatus, 'PTP') self.ptpeventproducer.publish_status(lastStatus, 'PTP')
# publish new event in API version v2 format # 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_PTP_LOCK_STATE) self.node_name, constants.SOURCE_SYNC_PTP_LOCK_STATE)
lastStatus = { lastStatus = {
'id': uuidutils.generate_uuid(), 'id': uuidutils.generate_uuid(),
@ -540,8 +589,46 @@ class PtpWatcherDefault:
] ]
} }
} }
self.ptpeventproducer.publish_status(lastStatus, constants.SOURCE_SYNC_PTP_LOCK_STATE) 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, 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 return

View File

@ -17,15 +17,15 @@ testpath = os.environ.get("TESTPATH", "")
phc2sys_test_config = constants.PTP_CONFIG_PATH + "phc2sys-phc2sys-test.conf" phc2sys_test_config = constants.PTP_CONFIG_PATH + "phc2sys-phc2sys-test.conf"
class OsClockMonitorTests(unittest.TestCase): 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): 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() self.clockmon.set_phc2sys_instance()
assert self.clockmon.phc2sys_instance == "phc2sys-test" assert self.clockmon.phc2sys_instance == "phc2sys-test"
def test_check_config_file_interface(self): 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.clockmon.phc2sys_config = testpath + "test_input_files/phc2sys-test.conf"
self.assertEqual(self.clockmon._check_config_file_interface(), "ens2f0") 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): def test_get_interface_phc_device(self, glob_patched):
# Success path # Success path
self.clockmon = OsClockMonitor(init=False) self.clockmon = OsClockMonitor(phc2sys_config=phc2sys_test_config, init=False)
self.clockmon.phc_interface = "ens1f0" self.clockmon.phc_interface = "ens1f0"
self.assertEqual(self.clockmon._get_interface_phc_device(), 'ptp0') 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', @mock.patch('trackingfunctionsdk.common.helpers.os_clock_monitor.subprocess.check_output',
side_effect=[b'-37000000015ns']) side_effect=[b'-37000000015ns'])
def test_get_os_clock_offset(self, subprocess_patched): 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.ptp_device = 'ptp0'
self.clockmon.get_os_clock_offset() self.clockmon.get_os_clock_offset()
assert self.clockmon.offset == '37000000015' assert self.clockmon.offset == '37000000015'
def test_set_os_closck_state(self): 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.offset = '37000000015'
self.clockmon.set_os_clock_state() self.clockmon.set_os_clock_state()
self.assertEqual(self.clockmon.get_os_clock_state(), OsClockState.Locked) self.assertEqual(self.clockmon.get_os_clock_state(), OsClockState.Locked)

View File

@ -21,16 +21,17 @@ cat <<EOF>/root/ptptracking-daemon.py
#!/usr/bin/python3 #!/usr/bin/python3
# -*- coding: UTF-8 -*- # -*- coding: UTF-8 -*-
import logging import logging
LOG = logging.getLogger(__name__)
from trackingfunctionsdk.common.helpers import log_helper
log_helper.config_logger(LOG)
import os import os
import json import json
from trackingfunctionsdk.common.helpers import log_helper
from trackingfunctionsdk.services.daemon import DaemonControl from trackingfunctionsdk.services.daemon import DaemonControl
LOG = logging.getLogger(__name__)
log_helper.config_logger(LOG)
THIS_NAMESPACE = os.environ.get("THIS_NAMESPACE", 'notification') THIS_NAMESPACE = os.environ.get("THIS_NAMESPACE", 'notification')
THIS_NODE_NAME = os.environ.get("THIS_NODE_NAME", 'controller-0') THIS_NODE_NAME = os.environ.get("THIS_NODE_NAME", 'controller-0')
THIS_POD_IP = os.environ.get("THIS_POD_IP", '127.0.0.1') 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_HOLDOVER_SECONDS = os.environ.get("OVERALL_HOLDOVER_SECONDS", 30)
OVERALL_POLL_FREQ_SECONDS = os.environ.get("OVERALL_POLL_FREQ_SECONDS", 2) 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") 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 = { context = {
'THIS_NAMESPACE': THIS_NAMESPACE, 'THIS_NAMESPACE': THIS_NAMESPACE,
@ -77,6 +80,7 @@ context = {
'NOTIFICATION_TRANSPORT_ENDPOINT': NOTIFICATION_TRANSPORT_ENDPOINT, 'NOTIFICATION_TRANSPORT_ENDPOINT': NOTIFICATION_TRANSPORT_ENDPOINT,
'GNSS_CONFIGS': GNSS_CONFIGS, 'GNSS_CONFIGS': GNSS_CONFIGS,
'PHC2SYS_CONFIG': PHC2SYS_CONFIG, 'PHC2SYS_CONFIG': PHC2SYS_CONFIG,
'PTP4L_CONFIGS' : PTP4L_CONFIGS,
'ptptracker_context': { 'ptptracker_context': {
'device_simulated': PTP_DEVICE_SIMULATED, 'device_simulated': PTP_DEVICE_SIMULATED,

View File

@ -147,6 +147,8 @@ spec:
value: "{{ .Values.ptptracking.ts2phcServiceName }}" value: "{{ .Values.ptptracking.ts2phcServiceName }}"
- name: TS2PHC_CONFIGS - name: TS2PHC_CONFIGS
value: '["/ptp/ptpinstance/ts2phc-{{.Values.ptptracking.ts2phcServiceName}}.conf"]' value: '["/ptp/ptpinstance/ts2phc-{{.Values.ptptracking.ts2phcServiceName}}.conf"]'
- name: LOGGING_LEVEL
value: "{{ .Values.ptptracking.log_level }}"
command: ["/bin/bash", "/mnt/ptptracking_start.sh"] command: ["/bin/bash", "/mnt/ptptracking_start.sh"]
securityContext: securityContext:
privileged: true privileged: true

View File

@ -70,6 +70,7 @@ ptptracking:
ptp4lServiceName: ptp4l-legacy ptp4lServiceName: ptp4l-legacy
phc2sysServiceName: phc2sys-legacy phc2sysServiceName: phc2sys-legacy
ts2phcServiceName: ts2phc-legacy ts2phcServiceName: ts2phc-legacy
log_level: INFO
image: image:
repository: starlingx/notificationservice-base repository: starlingx/notificationservice-base
tag: stx.7.0-v1.0.5 tag: stx.7.0-v1.0.5