
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
202 lines
8.0 KiB
Python
202 lines
8.0 KiB
Python
#
|
|
# 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()))
|