Update ptp-notification phc2sys HA clock source tracking

Check for the presence of a phc2sys source clock when phc2sys HA mode is
enabled and set lock state accordingly if no source is found.

For ptp-notification-v2, this change affects the os clock state
tracking. If HA mode is enabled and no clock source is present, proceed
to transition to HOLDOVER and FREERUN states as required.

For ptp-notification-v1, there has never been support for checking
whether phc2sys is synced/locked, only whether the service is running.
In order to leave the default behaviour the same, a user supplied helm
override PHC2SYS_COM_SOCKET can be set. By providing a path to the
phc2sys socket, ptp-notification-v1 will now check to see if a source
clock is present for phc2sys and transition to HOLDOVER and FREERUN if
no source is found. When the PHC2SYS_COM_SOCKET value is not provided,
ptp-notification-v1 maintains the default behaviour and does not check
for the presence of a source clock.

Test Plan:
PASS: Build containers and helm charts
PASS: Deploy ptp-notification
PASS: v2 api - test state transition when source clock is
lost/returns
PASS: v1 api - validate that default behaviour is unchanged when
phc2sys com socket is not set
PASS: v1 api - verify state transition when phc2sys source clock is
lost/returns

Story: 2010723
Task: 48514

Signed-off-by: Cole Walker <cole.walker@windriver.com>
Change-Id: I769d36df024d207bf0f70efd11d68d8a8809f713
This commit is contained in:
Cole Walker 2023-07-31 09:56:54 -04:00
parent af89f51505
commit 0dae44b92d
6 changed files with 137 additions and 10 deletions

View File

@ -3,11 +3,13 @@
#
# SPDX-License-Identifier: Apache-2.0
#
import configparser
import datetime
import logging
import os
import subprocess
import re
import socket
import subprocess
from glob import glob
from trackingfunctionsdk.common.helpers import log_helper
@ -19,23 +21,37 @@ log_helper.config_logger(LOG)
class OsClockMonitor:
_state = OsClockState()
last_event_time = None
phc2sys_instance = None
phc2sys_config = None
phc_interface = None
ptp_device = None
offset = None
def __init__(self, phc2sys_config, init=True):
self._state = OsClockState()
self.last_event_time = None
self.phc2sys_instance = None
self.phc_interface = None
self.ptp_device = None
self.offset = None
self.phc2sys_config = phc2sys_config
self.config = None
self.phc2sys_ha_enabled = False
self.phc2sys_com_socket = None
self.set_phc2sys_instance()
"""Normally initialize all fields, but allow these to be skipped to
assist with unit testing or to short-circuit values if required.
"""
if init:
self.get_os_clock_time_source()
self.parse_phc2sys_config()
if 'global' not in self.config.keys():
self.phc2sys_ha_enabled = False
elif 'ha_enabled' in self.config['global'].keys() \
and self.config['global']['ha_enabled'] == '1':
self.phc2sys_ha_enabled = True
self.phc2sys_com_socket = self.config['global'].get('ha_phc2sys_com_socket', None)
if self.phc2sys_ha_enabled is True:
self.set_phc2sys_ha_interface_and_phc
else:
self.get_os_clock_time_source()
self.get_os_clock_offset()
self.set_os_clock_state()
@ -46,6 +62,55 @@ class OsClockMonitor:
LOG.debug("phc2sys config file: %s" % self.phc2sys_config)
LOG.debug("phc2sys instance name: %s" % self.phc2sys_instance)
def parse_phc2sys_config(self):
LOG.debug("Parsing %s" % self.phc2sys_config)
config = configparser.ConfigParser(delimiters=' ')
config.read(self.phc2sys_config)
self.config = config
def query_phc2sys_socket(self, query, unix_socket=None):
if unix_socket:
try:
client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client_socket.connect(unix_socket)
client_socket.send(query.encode())
response = client_socket.recv(1024)
response = response.decode()
if response == "None":
response = None
return response
except ConnectionRefusedError as err:
LOG.error("Error connecting to phc2sys socket for instance %s: %s" % (
self.phc2sys_instance, err))
return None
except FileNotFoundError as err:
LOG.error("Error connecting to phc2sys socket for instance %s: %s" % (
self.phc2sys_instance, err))
return None
finally:
if hasattr(client_socket, 'close'):
client_socket.close()
else:
LOG.warning("No socket path supplied for instance %s" % self.instance_name)
return None
def set_phc2sys_ha_interface_and_phc(self):
update_phc_interface = self.query_phc2sys_socket('clock source', self.phc2sys_com_socket)
if update_phc_interface is None:
LOG.info("No PHC device found for HA phc2sys, status is FREERUN.")
self._state = OsClockState.Freerun
self.phc_interface = update_phc_interface
self.ptp_device = None
elif update_phc_interface != self.phc_interface:
LOG.info("Phc2sys source interface changed from %s to %s"
% (self.phc_interface, update_phc_interface))
self.phc_interface = update_phc_interface
if self.phc_interface is not None:
self.ptp_device = self._get_interface_phc_device()
LOG.debug("Phc2sys HA interface: %s ptp_device: %s" % (self.phc_interface, self.ptp_device))
def get_os_clock_time_source(self, pidfile_path="/var/run/"):
"""Determine which PHC is disciplining the OS clock"""
self.phc_interface = None
@ -104,7 +169,7 @@ class OsClockMonitor:
if len(ptp_device) == 0:
# Try the 0th interface instead, required for some NIC types
phc_interface_base = self.phc_interface[:-1] + "0"
LOG.error("No ptp device found at %s trying %s instead"
LOG.info("No ptp device found at %s trying %s instead"
% (pattern, phc_interface_base))
pattern = "/hostsys/class/net/" + phc_interface_base + \
"/device/ptp/*"
@ -122,6 +187,11 @@ class OsClockMonitor:
def get_os_clock_offset(self):
"""Get the os CLOCK_REALTIME offset"""
if self.phc2sys_ha_enabled is True:
# Refresh the HA source interface before checking offset
self.set_phc2sys_ha_interface_and_phc()
if self.ptp_device is None:
# This may happen in virtualized environments
LOG.warning("No PTP device. Defaulting offset value to 0.")

View File

@ -0,0 +1,10 @@
[global]
##
## Default Data Set
##
domainNumber 24
message_tag phc-inst1
uds_address /var/run/ptp4l-ptp-inst1
ha_enabled 1
[ens2f0]

View File

@ -24,6 +24,16 @@ class OsClockMonitorTests(unittest.TestCase):
self.clockmon.set_phc2sys_instance()
assert self.clockmon.phc2sys_instance == "phc2sys-test"
def test_parse_phc2sys_config(self):
self.clockmon = OsClockMonitor(phc2sys_config=phc2sys_test_config, init=False)
self.clockmon.phc2sys_config = testpath + "test_input_files/phc2sys-test.conf"
self.clockmon.parse_phc2sys_config()
assert 'ha_enabled' not in self.clockmon.config['global'].keys()
self.clockmon.phc2sys_config = testpath + "test_input_files/phc2sys-ha-test.conf"
self.clockmon.parse_phc2sys_config()
assert 'ha_enabled' in self.clockmon.config['global'].keys()
def test_check_config_file_interface(self):
self.clockmon = OsClockMonitor(phc2sys_config=phc2sys_test_config, init=False)
self.clockmon.phc2sys_config = testpath + "test_input_files/phc2sys-test.conf"

View File

@ -12,8 +12,10 @@
# Sync status provided as: 'Locked', 'Holdover', 'Freerun'
#
#
import configparser
import errno, os
import os.path
import socket
import sys
import subprocess
import datetime
@ -36,6 +38,9 @@ ptp_oper_dict = {
ptp4l_service_name = os.environ.get('PTP4L_SERVICE_NAME', 'ptp4l')
phc2sys_service_name = os.environ.get('PHC2SYS_SERVICE_NAME', 'phc2sys')
phc2sys_com_socket = os.environ.get('PHC2SYS_COM_SOCKET', "false")
phc2sys_config_file_path = '%sphc2sys-%s.conf' % (constants.LINUXPTP_CONFIG_PATH,
phc2sys_service_name)
# run subprocess and returns out, err, errcode
def run_shell2(dir, ctx, args):
@ -65,8 +70,37 @@ def check_critical_resources():
phc2sys = True
if os.path.isfile('%sptp4l-%s.conf' % (constants.LINUXPTP_CONFIG_PATH, ptp4l_service_name)):
ptp4lconf = True
if phc2sys_com_socket != "false":
# User enabled phc2sys HA source clock validation
phc2sys_source_clock = check_phc2sys_ha_source()
if phc2sys_source_clock is None:
phc2sys = False
LOG.warning("No Phc2sys HA source clock found")
return pmc, ptp4l, phc2sys, ptp4lconf
def check_phc2sys_ha_source():
query = 'clock source'
try:
client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client_socket.connect(phc2sys_com_socket)
client_socket.send(query.encode())
response = client_socket.recv(1024)
response = response.decode()
if response == "None":
response = None
return response
except ConnectionRefusedError as err:
LOG.error("Error connecting to phc2sys socket for instance %s: %s" % (
phc2sys_service_name, err))
return None
except FileNotFoundError as err:
LOG.error("Error connecting to phc2sys socket for instance %s: %s" % (
phc2sys_service_name, err))
return None
finally:
if hasattr(client_socket, 'close'):
client_socket.close()
def check_results(result, total_ptp_keywords, port_count):
# sync state is in 'Locked' state and will be overwritten if
# it is not the case

View File

@ -219,6 +219,8 @@ spec:
value: "{{ .Values.ptptracking.ptp4lServiceName }}"
- name: PHC2SYS_SERVICE_NAME
value: "{{ .Values.ptptracking.phc2sysServiceName }}"
- name: PHC2SYS_COM_SOCKET
value: "{{ .Values.ptptracking.phc2sysComSocket }}"
- name: PYTHONPATH
value: "/opt/ptptrackingfunction"
- name: LOGGING_LEVEL

View File

@ -71,6 +71,7 @@ ptptracking:
ptp4lSocket: /var/run/ptp4l-ptp4l-legacy
ptp4lServiceName: ptp4l-legacy
phc2sysServiceName: phc2sys-legacy
phc2sysComSocket: False
logging_level: INFO
image:
repository: starlingx/notificationservice-base