diff --git a/debian_stable_docker_images.inc b/debian_stable_docker_images.inc index ff7bc4f..4ae74b4 100644 --- a/debian_stable_docker_images.inc +++ b/debian_stable_docker_images.inc @@ -1,3 +1,4 @@ notificationservice-base +notificationservice-base-v2 locationservice-base notificationclient-base diff --git a/notificationservice-base-v2/centos/Dockerfile b/notificationservice-base-v2/centos/Dockerfile new file mode 100644 index 0000000..01339eb --- /dev/null +++ b/notificationservice-base-v2/centos/Dockerfile @@ -0,0 +1,23 @@ +ARG BASE +FROM ${BASE} + +ARG STX_REPO_FILE=/etc/yum.repos.d/stx.repo + +ENV KUBE_LATEST_VERSION="v1.23.1" + +RUN set -ex ;\ + yum install --disablerepo=* \ + $(grep '^name=' ${STX_REPO_FILE} | awk -F '=' '{printf "--enablerepo=" $2 " "}') \ + -y \ + gcc python3-devel python3-pip \ + && pip3 install --upgrade pip \ + && pip3 install --user pecan \ + && pip3 install oslo-config \ + && pip3 install oslo-messaging \ + && pip3 install WSME + +WORKDIR /opt/ +COPY ./ptptrackingfunction /opt/ptptrackingfunction +RUN cd /opt/ptptrackingfunction && python3 setup.py develop + +CMD ["bash"] diff --git a/notificationservice-base-v2/centos/notificationservice-base.stable_docker_image b/notificationservice-base-v2/centos/notificationservice-base.stable_docker_image new file mode 100644 index 0000000..e028c69 --- /dev/null +++ b/notificationservice-base-v2/centos/notificationservice-base.stable_docker_image @@ -0,0 +1,4 @@ +BUILDER=docker +LABEL=notificationservice-base +DOCKER_CONTEXT=../docker +DOCKER_FILE=./Dockerfile \ No newline at end of file diff --git a/notificationservice-base-v2/debian/Dockerfile b/notificationservice-base-v2/debian/Dockerfile new file mode 100644 index 0000000..f60fc39 --- /dev/null +++ b/notificationservice-base-v2/debian/Dockerfile @@ -0,0 +1,23 @@ +ARG BASE +FROM ${BASE} + +ENV DEBIAN_FRONTEND=noninteractive +ENV KUBE_LATEST_VERSION="v1.23.1" + +RUN apt-get -y update \ + && apt-get -y install \ + gcc \ + python3-dev \ + python3 \ + && apt-get -y clean \ + && rm -rf /var/lib/apt/lists/* +RUN pip3 install --user pecan \ + && pip3 install oslo-config \ + && pip3 install oslo-messaging \ + && pip3 install WSME + +WORKDIR /opt/ +COPY ./ptptrackingfunction /opt/ptptrackingfunction +RUN cd /opt/ptptrackingfunction && python3 setup.py develop + +CMD ["bash"] diff --git a/notificationservice-base-v2/debian/notificationservice-base-v2.stable_docker_image b/notificationservice-base-v2/debian/notificationservice-base-v2.stable_docker_image new file mode 100644 index 0000000..ee16275 --- /dev/null +++ b/notificationservice-base-v2/debian/notificationservice-base-v2.stable_docker_image @@ -0,0 +1,4 @@ +BUILDER=docker +LABEL=notificationservice-base-v2 +DOCKER_CONTEXT=../docker +DOCKER_FILE=./Dockerfile diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/LICENSE b/notificationservice-base-v2/docker/ptptrackingfunction/LICENSE new file mode 100644 index 0000000..6526f69 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 Wind River Systems, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/setup.cfg b/notificationservice-base-v2/docker/ptptrackingfunction/setup.cfg new file mode 100644 index 0000000..19c8281 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/setup.cfg @@ -0,0 +1,6 @@ +[nosetests] +match=^test +where=ptptrackingfunction +nocapture=1 +cover-package=ptptrackingfunction +cover-erase=1 diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/setup.py b/notificationservice-base-v2/docker/ptptrackingfunction/setup.py new file mode 100644 index 0000000..a883f59 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/setup.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +try: + from setuptools import setup, find_packages +except ImportError: + from ez_setup import use_setuptools + use_setuptools() + from setuptools import setup, find_packages + +setup( + name='ptptrackingfunction', + version='0.1', + description='', + author='', + author_email='', + install_requires=[ + "", + ], + test_suite='ptptrackingfunction', + zip_safe=False, + include_package_data=True, + packages=find_packages(exclude=['ez_setup']) +) diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/__init__.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/__init__.py new file mode 100644 index 0000000..ed5f938 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/__init__.py @@ -0,0 +1,5 @@ +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/client/base.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/client/base.py new file mode 100644 index 0000000..7d13e46 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/client/base.py @@ -0,0 +1,113 @@ +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import os +import json +import time +import oslo_messaging +from oslo_config import cfg +from trackingfunctionsdk.common.helpers import rpc_helper +from trackingfunctionsdk.model.dto.rpc_endpoint import RpcEndpointInfo + +import logging + +LOG = logging.getLogger(__name__) + +from trackingfunctionsdk.common.helpers import log_helper +log_helper.config_logger(LOG) + +class BrokerClientBase(object): + def __init__(self, broker_name, broker_transport_endpoint): + self.broker_name = broker_name + self.listeners = {} + self.broker_endpoint = RpcEndpointInfo(broker_transport_endpoint) + self.transport = rpc_helper.get_transport(self.broker_endpoint) + LOG.debug("Created Broker client:{0}".format(broker_name)) + + def __del__(self): + self.transport.cleanup() + del self.transport + return + + def __create_listener(self, context): + target = oslo_messaging.Target( + topic=context['topic'], + server=context['server']) + endpoints = context['endpoints'] + server = oslo_messaging.get_rpc_server( + self.transport, target, endpoints, executor=None) + return server + + def _refresh(self): + for topic, servers in self.listeners.items(): + for servername, context in servers.items(): + try: + rpcserver = context.get('rpcserver', None) + isactive = context.get('active', False) + if isactive and not rpcserver: + rpcserver = self.__create_listener(context) + rpcserver.start() + context['rpcserver'] = rpcserver + LOG.debug("Started rpcserver@{0}@{1}".format(context['topic'], context['server'])) + elif not isactive and rpcserver: + rpcserver.stop() + rpcserver.wait() + context.pop('rpcserver') + LOG.debug("Stopped rpcserver@{0}@{1}".format(context['topic'], context['server'])) + except: + LOG.error("Failed to update listener for topic/server:{0}/{1}" + .format(topic, servername)) + continue + + def add_listener(self, topic, server, listener_endpoints=None): + context = self.listeners.get(topic,{}).get(server, {}) + if not context: + context = { + 'endpoints': listener_endpoints, + 'topic': topic, + 'server': server, + 'active': True + } + if not self.listeners.get(topic, None): + self.listeners[topic] = {} + self.listeners[topic][server] = context + else: + context['endpoints'] = listener_endpoints + context['active'] = True + + self._refresh() + + def remove_listener(self, topic, server): + context = self.listeners.get(topic,{}).get(server, {}) + if context: + context['active'] = False + self._refresh() + + def is_listening(self, topic, server): + context = self.listeners.get(topic,{}).get(server, {}) + return context.get('active', False) + + def any_listener(self): + for topic, servers in self.listeners.items(): + for servername, context in servers.items(): + isactive = context.get('active', False) + if isactive: + return True + return False + + def call(self, topic, server, api_name, timeout=2, retry=0, **api_kwargs): + target = oslo_messaging.Target( + topic=topic, server=server, version=self.broker_endpoint.Version, + namespace=self.broker_endpoint.Namespace) + queryclient = oslo_messaging.RPCClient(self.transport, target, timeout = timeout, retry = retry) + return queryclient.call({}, api_name, **api_kwargs) + + def cast(self, topic, api_name, **api_kwargs): + target = oslo_messaging.Target( + topic=topic, fanout=True, version=self.broker_endpoint.Version, + namespace=self.broker_endpoint.Namespace) + queryclient = oslo_messaging.RPCClient(self.transport, target) + queryclient.cast({}, api_name, **api_kwargs) diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/client/ptpeventproducer.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/client/ptpeventproducer.py new file mode 100644 index 0000000..e177029 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/client/ptpeventproducer.py @@ -0,0 +1,203 @@ +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import os +import json +import time +import oslo_messaging +from oslo_config import cfg + +from trackingfunctionsdk.client.base import BrokerClientBase + +import logging + +LOG = logging.getLogger(__name__) + +from trackingfunctionsdk.common.helpers import log_helper + +log_helper.config_logger(LOG) + + +class PtpEventProducer(object): + class ListenerEndpoint(object): + target = oslo_messaging.Target(namespace='notification', version='1.0') + + def __init__(self, handler=None): + + self.handler = handler + self.init_time = time.time() + pass + + def QueryStatus(self, ctx, **rpc_kwargs): + LOG.debug("PtpEventProducer QueryStatus called %s" % rpc_kwargs) + if self.handler: + return self.handler.query_status(**rpc_kwargs) + else: + return None + + def TriggerDelivery(self, ctx, **rpc_kwargs): + LOG.debug("PtpEventProducer TriggerDelivery called %s" % rpc_kwargs) + if self.handler: + return self.handler.trigger_delivery(**rpc_kwargs) + else: + return None + + def __init__(self, node_name, local_broker_transport_endpoint, + registration_broker_transport_endpoint=None): + self.Id = id(self) + self.node_name = node_name + self.local_broker_client = BrokerClientBase( + 'LocalPtpEventProducer', local_broker_transport_endpoint) + if registration_broker_transport_endpoint: + self.registration_broker_client = BrokerClientBase( + 'AllPtpEventProducer', registration_broker_transport_endpoint) + else: + self.registration_broker_client = None + return + + def __del__(self): + if self.local_broker_client: + del self.local_broker_client + self.local_broker_client = None + if self.registration_broker_client: + del self.registration_broker_client + self.registration_broker_client = None + return + + def publish_status(self, ptpstatus, retry=3): + result = False + result1 = self.publish_status_local(ptpstatus, + retry) if self.local_broker_client else result + result2 = self.publish_status_all(ptpstatus, + retry) if self.registration_broker_client else result + return result1, result2 + + def publish_status_local(self, ptpstatus, source, retry=3): + if not self.local_broker_client: + return False + topic = '{0}-Event-{1}'.format(source, self.node_name) + server = None + isretrystopped = False + while not isretrystopped: + try: + self.local_broker_client.cast( + topic, 'NotifyStatus', notification=ptpstatus) + LOG.debug("Published ptp status:{0}@Topic:{1}".format(ptpstatus, topic)) + break + except Exception as ex: + LOG.warning("Failed to publish ptp status:{0}@Topic:{1} due to: {2}".format( + ptpstatus, topic, str(ex))) + retry = retry - 1 + isretrystopped = False if retry > 0 else True + + if isretrystopped: + LOG.error("Failed to publish ptp status:{0}@Topic:{1}".format( + ptpstatus, topic)) + return isretrystopped == False + + def publish_status_all(self, ptpstatus, retry=3): + if not self.registration_broker_client: + return False + topic_all = 'PTP-Event-*' + server = None + isretrystopped = False + while not isretrystopped: + try: + self.registration_broker_client.cast( + topic_all, 'NotifyStatus', notification=ptpstatus) + LOG.debug("Published ptp status:{0}@Topic:{1}".format(ptpstatus, topic_all)) + break + except Exception as ex: + LOG.warning("Failed to publish ptp status:{0}@Topic:{1} due to: {2}".format( + ptpstatus, topic_all, str(ex))) + retry = retry - 1 + isretrystopped = False if retry > 0 else True + + if isretrystopped: + LOG.error("Failed to publish ptp status:{0}@Topic:{1}".format( + ptpstatus, topic_all)) + return isretrystopped == False + + def start_status_listener(self, handler=None): + result = False + result1 = self.start_status_listener_local(handler) if self.local_broker_client else result + result2 = self.start_status_listener_all( + handler) if self.registration_broker_client else result + result = result1 and result2 + return result + + def start_status_listener_local(self, handler=None): + if not self.local_broker_client: + return False + + topic = 'PTP-Status' + server = 'PTP-Tracking-{0}'.format(self.node_name) + endpoints = [PtpEventProducer.ListenerEndpoint(handler)] + + self.local_broker_client.add_listener( + topic, server, endpoints) + return True + + def start_status_listener_all(self, handler=None): + if not self.registration_broker_client: + return False + + topic = 'PTP-Status' + server = 'PTP-Tracking-{0}'.format(self.node_name) + endpoints = [PtpEventProducer.ListenerEndpoint(handler)] + + self.registration_broker_client.add_listener( + topic, server, endpoints) + return True + + def stop_status_listener(self): + result = False + result1 = self.stop_status_listener_local() if self.local_broker_client else result + result2 = self.stop_status_listener_all() if self.registration_broker_client else result + result = result1 and result2 + return result + + def stop_status_listener_local(self): + if not self.local_broker_client: + return False + + topic = 'PTP-Status' + server = "PTP-Tracking-{0}".format(self.node_name) + self.local_broker_client.remove_listener( + topic, server) + + def stop_status_listener_all(self): + if not self.registration_broker_client: + return False + + topic = 'PTP-Status' + server = "PTP-Tracking-{0}".format(self.node_name) + self.registration_broker_client.remove_listener( + topic, server) + + def is_listening(self): + result = False + result1 = self.is_listening_local() if self.local_broker_client else result + result2 = self.is_listening_all() if self.registration_broker_client else result + result = result1 and result2 + return result + + def is_listening_local(self): + if not self.local_broker_client: + return False + + topic = 'PTP-Status' + server = "PTP-Tracking-{0}".format(self.node_name) + return self.local_broker_client.is_listening( + topic, server) + + def is_listening_all(self): + if not self.registration_broker_client: + return False + topic = 'PTP-Status' + server = "PTP-Tracking-{0}".format(self.node_name) + return self.registration_broker_client.is_listening( + topic, server) diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/__init__.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/__init__.py similarity index 100% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/__init__.py rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/__init__.py diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/__init__.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/__init__.py similarity index 100% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/__init__.py rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/__init__.py diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/cgu_handler.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/cgu_handler.py similarity index 99% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/cgu_handler.py rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/cgu_handler.py index a3088dc..24ea1dd 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/cgu_handler.py +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/cgu_handler.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 Wind River Systems, Inc. +# Copyright (c) 2022-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/constants.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/constants.py new file mode 100644 index 0000000..2a57ad3 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/constants.py @@ -0,0 +1,75 @@ +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from os import path + +# phc states constants +FREERUN_PHC_STATE = "Freerun" +LOCKED_PHC_STATE = "Locked" +HOLDOVER_PHC_STATE = "Holdover" +UNKNOWN_PHC_STATE = "Unknown" +# PMC command constants +PORT_STATE = "portState" +PORT = "port{}" +GM_PRESENT = "gmPresent" +MASTER_OFFSET = "master_offset" +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" +MASTER_MODE = "master" +TIME_IS_TRACEABLE1 = "1" +TIME_IS_TRACEABLE2 = "true" +GM_IS_PRESENT = "true" +CLOCK_CLASS_VALUE6 = "6" +# ts2phc constants +NMEA_SERIALPORT = "ts2phc.nmea_serialport" +GNSS_PIN = "GNSS-1PPS" +GNSS_LOCKED_HO_ACK = 'locked_ho_ack' +GNSS_LOCKED_HO_ACQ = 'locked_ho_acq' +GNSS_DPLL_0 = "DPLL0" +GNSS_DPLL_1 = "DPLL1" + +UTC_OFFSET = "37" + +if path.exists('/ptp/linuxptp/ptpinstance'): + LINUXPTP_CONFIG_PATH = '/ptp/linuxptp/ptpinstance/' +elif path.exists('/ptp/ptpinstance'): + LINUXPTP_CONFIG_PATH = '/ptp/ptpinstance/' +else: + LINUXPTP_CONFIG_PATH = '/ptp/' +PTP_CONFIG_PATH = LINUXPTP_CONFIG_PATH +PHC2SYS_CONFIG_PATH = LINUXPTP_CONFIG_PATH +TS2PHC_CONFIG_PATH = LINUXPTP_CONFIG_PATH +PHC_CTL_PATH = "/usr/sbin/phc_ctl" +PHC2SYS_DEFAULT_CONFIG = PHC2SYS_CONFIG_PATH + "phc2sys-phc2sys-legacy.conf" + +CLOCK_REALTIME = "CLOCK_REALTIME" + +PHC2SYS_TOLERANCE_LOW = 36999999000 +PHC2SYS_TOLERANCE_HIGH = 37000001000 + +PTP_V1_KEY = "ptp_notification_v1" + +SPEC_VERSION = "1.0" +DATA_VERSION = "1.0" +DATA_TYPE_NOTIFICATION = "notification" +DATA_TYPE_METRIC = "metric" +VALUE_TYPE_ENUMERATION = "enumeration" +VALUE_TYPE_METRIC = "metric" + +SOURCE_SYNC_ALL = '/sync' +SOURCE_SYNC_GNSS_SYNC_STATUS = '/sync/gnss-status/gnss-sync-status' +SOURCE_SYNC_PTP_CLOCK_CLASS = '/sync/ptp-status/clock-class' +SOURCE_SYNC_PTP_LOCK_STATE = '/sync/ptp-status/lock-state' +SOURCE_SYNC_OS_CLOCK = '/sync/sync-status/os-clock-sync-state' +SOURCE_SYNC_SYNC_STATE = '/sync/sync-status/sync-state' +SOURCE_SYNCE_CLOCK_QUALITY = '/sync/synce-status/clock-quality' +SOURCE_SYNCE_LOCK_STATE_EXTENDED = '/sync/synce-status/lock-state-extended' +SOURCE_SYNCE_LOCK_STATE = '/sync/synce-status/lock-state' diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/gnss_monitor.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/gnss_monitor.py similarity index 98% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/gnss_monitor.py rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/gnss_monitor.py index 732ac36..172cb9d 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/gnss_monitor.py +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/gnss_monitor.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 Wind River Systems, Inc. +# Copyright (c) 2022-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py new file mode 100644 index 0000000..f0fb631 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py @@ -0,0 +1,22 @@ +# +# Copyright (c) 2021-2023 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): + logging.basicConfig(stream=sys.stdout, + format='%(asctime)s %(levelname)-8s %(message)s', + datefmt='%Y-%m-%d %H:%M:%S') + logger.setLevel(level=os.environ.get("LOGGING_LEVEL", "INFO")) + return logger diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/os_clock_monitor.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/os_clock_monitor.py similarity index 99% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/os_clock_monitor.py rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/os_clock_monitor.py index 7a47a1e..ba2e7a9 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/os_clock_monitor.py +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/os_clock_monitor.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 Wind River Systems, Inc. +# Copyright (c) 2022-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptp_monitor.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptp_monitor.py similarity index 99% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptp_monitor.py rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptp_monitor.py index 7fe0c10..38d4a64 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptp_monitor.py +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptp_monitor.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2021-2022 Wind River Systems, Inc. +# Copyright (c) 2021-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py new file mode 100644 index 0000000..5b659a3 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py @@ -0,0 +1,107 @@ +#! /usr/bin/python3 +# +# Copyright (c) 2021-2023 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 os +import re +import subprocess +import logging +from trackingfunctionsdk.common.helpers import constants +from trackingfunctionsdk.common.helpers import log_helper + +LOG = logging.getLogger(__name__) +log_helper.config_logger(LOG) + + +# run subprocess and returns out, err, errcode +def run_shell2(dir, ctx, args): + cwd = os.getcwd() + os.chdir(dir) + + process = subprocess.Popen(args, shell=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = process.communicate() + errcode = process.returncode + + os.chdir(cwd) + + return out, err, errcode + + +def check_critical_resources(ptp4l_service_name, phc2sys_service_name): + pmc = False + ptp4l = False + phc2sys = False + ptp4lconf = False + + if os.path.isfile('/usr/sbin/pmc'): + pmc = True + if os.path.isfile('/var/run/ptp4l-%s.pid' % ptp4l_service_name): + ptp4l = True + if os.path.isfile('/var/run/phc2sys-%s.pid' % phc2sys_service_name): + phc2sys = True + if os.path.isfile(constants.PTP_CONFIG_PATH + + ('ptp4l-%s.conf' % ptp4l_service_name)): + ptp4lconf = True + return pmc, ptp4l, phc2sys, ptp4lconf + + +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 + sync_state = constants.LOCKED_PHC_STATE + + local_gm = False + + # check for a healthy result + if len(result) != total_ptp_keywords: + sync_state = constants.FREERUN_PHC_STATE + LOG.warning('results are not complete, returning FREERUN') + return sync_state + # determine the current sync state + if (result[constants.GM_PRESENT].lower() != constants.GM_IS_PRESENT + and result[constants.GRANDMASTER_IDENTITY] != result[constants.CLOCK_IDENTITY]): + sync_state = constants.FREERUN_PHC_STATE + elif result[constants.GRANDMASTER_IDENTITY] == result[constants.CLOCK_IDENTITY]: + 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: + break + elif local_gm and result[constants.PORT.format(port)].lower() == constants.MASTER_MODE: + break + else: + sync_state = constants.FREERUN_PHC_STATE + if (result[constants.TIME_TRACEABLE] != constants.TIME_IS_TRACEABLE1 + and result[constants.TIME_TRACEABLE].lower != constants.TIME_IS_TRACEABLE2): + sync_state = constants.FREERUN_PHC_STATE + if (result[constants.GM_CLOCK_CLASS] not in + [constants.CLOCK_CLASS_VALUE6]): + sync_state = constants.FREERUN_PHC_STATE + return sync_state + + +def parse_resource_address(resource_address): + # The format of resource address is: + # /{clusterName}/{siteName}(/optional/hierarchy/..)/{nodeName}/{resource} + # Assume no optional hierarchy for now + clusterName = resource_address.split('/')[1] + nodeName = resource_address.split('/')[2] + resource_path = '/' + re.split('[/]', resource_address, 3)[3] + return clusterName, nodeName, resource_path + + +def format_resource_address(node_name, resource): + # Return a resource_address + resource_address = '/./' + node_name + resource + return resource_address diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/rpc_helper.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/rpc_helper.py new file mode 100644 index 0000000..0a85a5c --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/rpc_helper.py @@ -0,0 +1,24 @@ +#coding=utf-8 +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import oslo_messaging +from oslo_config import cfg + + +def setup_client(rpc_endpoint_info, topic, server): + oslo_messaging.set_transport_defaults(rpc_endpoint_info.Exchange) + transport = oslo_messaging.get_rpc_transport(cfg.CONF, url=rpc_endpoint_info.TransportEndpoint) + target = oslo_messaging.Target(topic=topic, + version=rpc_endpoint_info.Version, + server=server, + namespace=rpc_endpoint_info.Namespace) + client = oslo_messaging.RPCClient(transport, target) + return client + +def get_transport(rpc_endpoint_info): + oslo_messaging.set_transport_defaults(rpc_endpoint_info.Exchange) + return oslo_messaging.get_rpc_transport(cfg.CONF, url=rpc_endpoint_info.TransportEndpoint) diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/__init__.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/__init__.py new file mode 100644 index 0000000..ed5f938 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/__init__.py @@ -0,0 +1,5 @@ +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/__init__.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/__init__.py similarity index 100% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/__init__.py rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/__init__.py diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/gnssstate.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/gnssstate.py similarity index 100% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/gnssstate.py rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/gnssstate.py diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/osclockstate.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/osclockstate.py similarity index 100% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/osclockstate.py rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/osclockstate.py diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/overallclockstate.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/overallclockstate.py similarity index 83% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/overallclockstate.py rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/overallclockstate.py index c4ee0ef..85ee708 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/overallclockstate.py +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/overallclockstate.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 Wind River Systems, Inc. +# Copyright (c) 2022-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstate.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstate.py new file mode 100644 index 0000000..5415ac4 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstate.py @@ -0,0 +1,11 @@ +#coding=utf-8 + +from wsme import types as wtypes + +EnumPtpState = wtypes.Enum(str, 'Locked', 'Freerun', 'Holdover') + +class PtpState(object): + Locked = "Locked" + Freerun = "Freerun" + Holdover = "Holdover" + diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstatus.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstatus.py new file mode 100644 index 0000000..0be94fc --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstatus.py @@ -0,0 +1,24 @@ +#coding=utf-8 + +from wsme import types as wtypes +from trackingfunctionsdk.model.dto.resourcetype import EnumResourceType +from trackingfunctionsdk.model.dto.ptpstate import PtpState + +class PtpStatus(wtypes.Base): + EventTimestamp = float + ResourceType = EnumResourceType + EventData_State = PtpState + ResourceQualifier_NodeName = wtypes.text + + def to_dict(self): + d = { + 'EventTimestamp': self.EventTimestamp, + 'ResourceType': self.ResourceType, + 'EventData': { + 'State': self.EventData_State + }, + 'ResourceQualifier': { + 'NodeName': self.ResourceQualifier_NodeName + } + } + return d diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/resourcetype.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/resourcetype.py new file mode 100644 index 0000000..70b5763 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/resourcetype.py @@ -0,0 +1,10 @@ +#coding=utf-8 + +from wsme import types as wtypes + +EnumResourceType = wtypes.Enum(str, 'PTP', 'FPGA') + +class ResourceType(object): + TypePTP = "PTP" + TypeFPGA = "FPGA" + TypeGNSS = "GNSS" diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/rpc_endpoint.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/rpc_endpoint.py new file mode 100644 index 0000000..e4dcceb --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/rpc_endpoint.py @@ -0,0 +1,34 @@ +#coding=utf-8 + +from wsme import types as wtypes + +RPC_ENDPOINT_BASE = { + 'Version': '1.0', + 'Namespace': 'notification', + 'Exchange': 'notification_exchange', + 'TransportEndpoint': '', + 'Topic': '', + 'Server': '' +} + +class RpcEndpointInfo(wtypes.Base): + TransportEndpoint = wtypes.text + Exchange = wtypes.text + Topic = wtypes.text + Server = wtypes.text + Version = wtypes.text + Namespace = wtypes.text + + def __init__(self, transport_endpoint): + self.endpoint_json = { + 'Version': RPC_ENDPOINT_BASE['Version'], + 'Namespace': RPC_ENDPOINT_BASE['Namespace'], + 'Exchange': RPC_ENDPOINT_BASE['Exchange'], + 'TransportEndpoint': transport_endpoint, + 'Topic': RPC_ENDPOINT_BASE['Topic'], + 'Server': RPC_ENDPOINT_BASE['Server'] + } + super(RpcEndpointInfo, self).__init__(**self.endpoint_json) + + def to_dict(self): + return self.endpoint_json diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/services/__init__.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/services/__init__.py new file mode 100644 index 0000000..ed5f938 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/services/__init__.py @@ -0,0 +1,5 @@ +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py new file mode 100644 index 0000000..50af9d6 --- /dev/null +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py @@ -0,0 +1,800 @@ +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +import datetime +import json +import logging +import multiprocessing as mp +import os +import threading +import time +from oslo_utils import uuidutils + +from trackingfunctionsdk.client.ptpeventproducer import PtpEventProducer +from trackingfunctionsdk.common.helpers import constants +from trackingfunctionsdk.common.helpers import ptpsync as utils +from trackingfunctionsdk.common.helpers import log_helper +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 +from trackingfunctionsdk.model.dto.overallclockstate import OverallClockState +from trackingfunctionsdk.model.dto.resourcetype import ResourceType +from trackingfunctionsdk.model.dto.rpc_endpoint import RpcEndpointInfo + +LOG = logging.getLogger(__name__) +log_helper.config_logger(LOG) + +THIS_NODE_NAME = os.environ.get("THIS_NODE_NAME", 'controller-0') + +# Event source to event type mapping +source_type = { + '/sync/gnss-status/gnss-sync-status': + 'event.sync.gnss-status.gnss-state-change', + '/sync/ptp-status/clock-class': + 'event.sync.ptp-status.ptp-clock-class-change', + '/sync/ptp-status/lock-state': + 'event.sync.ptp-status.ptp-state-change', + '/sync/sync-status/os-clock-sync-state': + 'event.sync.sync-status.os-clock-sync-state-change', + '/sync/sync-status/sync-state': + 'event.sync.sync-status.synchronization-state-change', + '/sync/synce-status/clock-quality': + 'event.sync.synce-status.synce-clock-quality-change', + '/sync/synce-status/lock-state-extended': + 'event.sync.synce-status.synce-state-change-extended', + '/sync/synce-status/lock-state': + 'event.sync.synce-status.synce-state-change', +} + +'''Entry point of Default Process Worker''' + + +def ProcessWorkerDefault(event, sqlalchemy_conf_json, + broker_transport_endpoint): + worker = PtpWatcherDefault(event, sqlalchemy_conf_json, + broker_transport_endpoint) + worker.run() + + +class PtpWatcherDefault: + DEFAULT_PTPTRACKER_CONTEXT = { + 'holdover_seconds': 30, + 'poll_freq_seconds': 2 + } + + DEFAULT_GNSSTRACKER_CONTEXT = { + 'holdover_seconds': 30, + 'poll_freq_seconds': 2 + } + + DEFAULT_OS_CLOCK_TRACKER_CONTEXT = { + 'holdover_seconds': 30, + 'poll_freq_seconds': 2 + } + + DEFAULT_OVERALL_SYNC_TRACKER_CONTEXT = { + 'holdover_seconds': 30, + 'poll_freq_seconds': 2 + } + + class PtpRequestHandlerDefault(object): + def __init__(self, watcher, daemon_context): + self.watcher = watcher + self.init_time = time.time() + self.daemon_context = daemon_context + + def _build_event_response( + self, resource_path, last_event_time, resource_address, + sync_state, value_type=constants.VALUE_TYPE_ENUMERATION): + 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 + lastStatus = { + 'id': uuidutils.generate_uuid(), + 'specversion': constants.SPEC_VERSION, + 'source': resource_path, + 'type': source_type[resource_path], + 'time': last_event_time, + 'data': { + 'version': constants.DATA_VERSION, + 'values': [ + { + 'data_type': data_type, + 'ResourceAddress': resource_address, + 'value_type': value_type, + 'value': sync_state + } + ] + } + } + return lastStatus + + def query_status(self, **rpc_kwargs): + lastStatus = {} + resource_address = rpc_kwargs.get('ResourceAddress', None) + optional = rpc_kwargs.get('optional', None) + if 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() + if optional and self.watcher.gnsstracker_context.get(optional): + sync_state = \ + self.watcher.gnsstracker_context[optional].get( + 'sync_state', GnssState.Failure_Nofix) + last_event_time = \ + self.watcher.gnsstracker_context[optional].get( + 'last_event_time', time.time()) + lastStatus[optional] = self._build_event_response( + resource_path, last_event_time, resource_address, + sync_state) + elif not optional: + for config in self.daemon_context['GNSS_INSTANCES']: + sync_state = \ + self.watcher.gnsstracker_context[config].get( + 'sync_state', GnssState.Failure_Nofix) + last_event_time = \ + self.watcher.gnsstracker_context[config].get( + 'last_event_time', time.time()) + lastStatus[config] = self._build_event_response( + resource_path, last_event_time, + resource_address, sync_state) + else: + lastStatus = None + self.watcher.gnsstracker_context_lock.release() + elif resource_path == constants.SOURCE_SYNC_PTP_CLOCK_CLASS: + self.watcher.ptptracker_context_lock.acquire() + if optional and self.watcher.ptptracker_context.get(optional): + clock_class = \ + self.watcher.ptptracker_context[optional].get( + 'clock_class', '248') + last_clock_class_event_time = \ + self.watcher.ptptracker_context[optional].get( + 'last_clock_class_event_time', time.time()) + lastStatus[optional] = self._build_event_response( + resource_path, last_clock_class_event_time, + resource_address, clock_class, + constants.VALUE_TYPE_METRIC) + elif not optional: + for config in self.daemon_context['PTP4L_INSTANCES']: + clock_class = \ + self.watcher.ptptracker_context[config].get( + 'clock_class', '248') + last_clock_class_event_time = \ + self.watcher.ptptracker_context[config].get( + 'last_clock_class_event_time', + time.time()) + lastStatus[config] = self._build_event_response( + resource_path, last_clock_class_event_time, + resource_address, clock_class, + constants.VALUE_TYPE_METRIC) + else: + lastStatus = None + self.watcher.ptptracker_context_lock.release() + elif resource_path == constants.SOURCE_SYNC_PTP_LOCK_STATE: + self.watcher.ptptracker_context_lock.acquire() + if optional and self.watcher.ptptracker_context.get(optional): + sync_state = \ + self.watcher.ptptracker_context[optional].get( + 'sync_state', PtpState.Freerun) + last_event_time = \ + self.watcher.ptptracker_context[optional].get( + 'last_event_time', time.time()) + lastStatus[optional] = self._build_event_response( + resource_path, last_event_time, resource_address, + sync_state) + elif not optional: + for config in self.daemon_context['PTP4L_INSTANCES']: + sync_state = \ + self.watcher.ptptracker_context[config].get( + 'sync_state', PtpState.Freerun) + last_event_time = \ + self.watcher.ptptracker_context[config].get( + 'last_event_time', time.time()) + lastStatus[config] = self._build_event_response( + resource_path, last_event_time, + resource_address, sync_state) + else: + lastStatus = None + self.watcher.ptptracker_context_lock.release() + + 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()) + self.watcher.osclocktracker_context_lock.release() + lastStatus['os_clock_status'] = 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()) + self.watcher.overalltracker_context_lock.release() + lastStatus['overall_sync_status'] = \ + self._build_event_response( + resource_path, last_event_time, resource_address, + sync_state) + LOG.debug("query_status: {}".format(lastStatus)) + else: + # Request is for PTP v1 notification + # PTP v1 only supports single instance ptp + instance = self.daemon_context['PTP4L_INSTANCES'][0] + if len(self.daemon_context['PTP4L_INSTANCES']) > 1: + LOG.warning("Multiple ptp4l instances configured, " + "retrieving status for %s" % instance) + self.watcher.ptptracker_context_lock.acquire() + sync_state = self.watcher.ptptracker_context[instance].get( + 'sync_state', PtpState.Freerun) + last_event_time = \ + self.watcher.ptptracker_context[instance].get( + 'last_event_time', time.time()) + lastStatus[constants.PTP_V1_KEY] = { + 'ResourceType': ResourceType.TypePTP, + 'EventData': { + 'State': sync_state + }, + 'ResourceQualifier': { + 'NodeName': self.watcher.node_name + }, + 'EventTimestamp': last_event_time + } + self.watcher.ptptracker_context_lock.release() + LOG.warning("query_status PTP v1: {}".format(lastStatus)) + + return lastStatus + + def trigger_delivery(self, **rpc_kwargs): + self.watcher.forced_publishing = True + self.watcher.signal_ptp_event() + + def __init__(self, event, sqlalchemy_conf_json, daemon_context_json): + self.sqlalchemy_conf = json.loads(sqlalchemy_conf_json) + self.event = event + self.init_time = time.time() + + self.daemon_context = json.loads(daemon_context_json) + + # PTP Context + self.ptptracker_context = {} + for config in self.daemon_context['PTP4L_INSTANCES']: + self.ptptracker_context[config] = \ + PtpWatcherDefault.DEFAULT_PTPTRACKER_CONTEXT.copy() + self.ptptracker_context[config]['sync_state'] = PtpState.Freerun + self.ptptracker_context[config]['last_event_time'] = self.init_time + self.ptptracker_context[config]['holdover_seconds'] = \ + os.environ.get("PTP_HOLDOVER_SECONDS", 30) + self.ptptracker_context[config]['poll_freq_seconds'] = \ + os.environ.get("CONTROL_TIMEOUT", 2) + 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_INSTANCES']: + self.gnsstracker_context[config] = \ + PtpWatcherDefault.DEFAULT_GNSSTRACKER_CONTEXT.copy() + self.gnsstracker_context[config]['sync_state'] = \ + GnssState.Failure_Nofix + self.gnsstracker_context[config]['last_event_time'] = \ + self.init_time + self.gnsstracker_context[config]['holdover_seconds'] = \ + os.environ.get("GNSS_HOLDOVER_SECONDS", 30) + self.gnsstracker_context[config]['poll_freq_seconds'] = \ + os.environ.get("CONTROL_TIMEOUT", 2) + self.gnsstracker_context_lock = threading.Lock() + LOG.debug("gnsstracker_context: %s" % self.gnsstracker_context) + + # OS Clock Context + self.osclocktracker_context = {} + self.osclocktracker_context = \ + PtpWatcherDefault.DEFAULT_OS_CLOCK_TRACKER_CONTEXT.copy() + self.osclocktracker_context['sync_state'] = OsClockState.Freerun + self.osclocktracker_context['last_event_time'] = self.init_time + self.osclocktracker_context['holdover_seconds'] = \ + os.environ.get("OS_CLOCK_HOLDOVER_SECONDS", 30) + self.osclocktracker_context['poll_freq_seconds'] = \ + os.environ.get("CONTROL_TIMEOUT", 2) + self.osclocktracker_context_lock = threading.Lock() + + # Overall Sync Context + self.overalltracker_context = {} + self.overalltracker_context = \ + PtpWatcherDefault.DEFAULT_OVERALL_SYNC_TRACKER_CONTEXT.copy() + self.overalltracker_context['sync_state'] = OverallClockState.Freerun + self.overalltracker_context['last_event_time'] = self.init_time + self.overalltracker_context['holdover_seconds'] = \ + os.environ.get("OVERALL_HOLDOVER_SECONDS", 30) + self.overalltracker_context['poll_freq_seconds'] = \ + os.environ.get("CONTROL_TIMEOUT", 2) + self.overalltracker_context_lock = threading.Lock() + + self.event_timeout = float(os.environ.get('CONTROL_TIMEOUT', 2)) + + self.node_name = self.daemon_context['THIS_NODE_NAME'] + + self.namespace = self.daemon_context.get( + 'THIS_NAMESPACE', 'notification') + + broker_transport_endpoint = \ + self.daemon_context['NOTIFICATION_TRANSPORT_ENDPOINT'] + + registration_transport_endpoint = \ + self.daemon_context['REGISTRATION_TRANSPORT_ENDPOINT'] + + self.broker_endpoint = RpcEndpointInfo(broker_transport_endpoint) + self.registration_broker_endpoint = \ + RpcEndpointInfo(registration_transport_endpoint) + self.ptpeventproducer = PtpEventProducer( + self.node_name, + self.broker_endpoint.TransportEndpoint, + self.registration_broker_endpoint.TransportEndpoint) + + self.__ptprequest_handler = \ + PtpWatcherDefault.PtpRequestHandlerDefault( + self, self.daemon_context) + + # 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.observer_list = [ + GnssMonitor(i) for i in self.daemon_context['GNSS_CONFIGS']] + + # Setup OS Clock monitor + 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'], + self.daemon_context['PHC2SYS_SERVICE_NAME']) + for config in self.daemon_context['PTP4L_INSTANCES']] + + def signal_ptp_event(self): + if self.event: + self.event.set() + else: + LOG.warning("Unable to assert ptp event") + + def run(self): + # start location listener + self.__start_listener() + + while True: + # announce the location + forced = self.forced_publishing + self.forced_publishing = False + 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_overall_sync_status(forced) + if self.event.wait(self.event_timeout): + LOG.debug("daemon control event is asserted") + self.event.clear() + else: + LOG.debug("daemon control event is timeout") + continue + self.__stop_listener() + + '''Start listener to answer querying from clients''' + + def __start_listener(self): + LOG.debug("start listener to answer location querying") + + self.ptpeventproducer.start_status_listener( + self.__ptprequest_handler + ) + + def __stop_listener(self): + LOG.debug("stop listener to answer location querying") + + self.ptpeventproducer.stop_status_listener(self.location_info) + + def __get_gnss_status(self, holdover_time, freq, sync_state, + last_event_time, gnss_monitor): + new_event, sync_state, new_event_time = gnss_monitor.get_gnss_status( + holdover_time, freq, sync_state, last_event_time) + LOG.debug("Getting GNSS status.") + return new_event, sync_state, new_event_time + + def __get_os_clock_status(self, holdover_time, freq, sync_state, + last_event_time): + new_event, sync_state, new_event_time = \ + self.os_clock_monitor.os_clock_status( + holdover_time, freq, sync_state, last_event_time) + LOG.debug("Getting os clock status.") + return new_event, sync_state, new_event_time + + def __get_overall_sync_state(self, holdover_time, freq, sync_state, + last_event_time): + new_event = False + new_event_time = last_event_time + previous_sync_state = sync_state + current_time = datetime.datetime.utcnow().timestamp() + time_in_holdover = None + if previous_sync_state == constants.HOLDOVER_PHC_STATE: + time_in_holdover = round(current_time - last_event_time) + max_holdover_time = (holdover_time - freq * 2) + gnss_state = None + os_clock_state = None + ptp_state = None + + LOG.debug("Getting overall sync state.") + for gnss in self.observer_list: + if gnss._state == constants.UNKNOWN_PHC_STATE or \ + gnss._state == GnssState.Failure_Nofix: + gnss_state = GnssState.Failure_Nofix + elif gnss._state == GnssState.Synchronized and \ + gnss_state != GnssState.Failure_Nofix: + gnss_state = GnssState.Synchronized + + for ptp4l in self.ptp_monitor_list: + _, read_state, _ = ptp4l.get_ptp_sync_state() + if read_state == PtpState.Holdover or \ + read_state == PtpState.Freerun or \ + read_state == constants.UNKNOWN_PHC_STATE: + ptp_state = PtpState.Freerun + elif read_state == PtpState.Locked and \ + ptp_state != PtpState.Freerun: + ptp_state = PtpState.Locked + + os_clock_state = self.os_clock_monitor.get_os_clock_state() + + if gnss_state is GnssState.Failure_Nofix or \ + os_clock_state is OsClockState.Freerun or \ + ptp_state is PtpState.Freerun: + sync_state = OverallClockState.Freerun + else: + sync_state = OverallClockState.Locked + + if sync_state == OverallClockState.Freerun: + if previous_sync_state in [ + constants.UNKNOWN_PHC_STATE, + constants.FREERUN_PHC_STATE]: + sync_state = OverallClockState.Freerun + elif previous_sync_state == constants.LOCKED_PHC_STATE: + sync_state = OverallClockState.Holdover + elif previous_sync_state == constants.HOLDOVER_PHC_STATE and \ + time_in_holdover < max_holdover_time: + LOG.debug("Overall sync: Time in holdover is %s " + "Max time in holdover is %s" + % (time_in_holdover, max_holdover_time)) + sync_state = OverallClockState.Holdover + else: + sync_state = OverallClockState.Freerun + + if sync_state != previous_sync_state: + new_event = True + 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, ptp_monitor): + new_event = False + new_event_time = last_event_time + ptp_monitor.set_ptp_sync_state() + if self.ptp_device_simulated: + now = time.time() + timediff = now - last_event_time + if timediff > holdover_time: + new_event = True + new_event_time = now + if sync_state == PtpState.Freerun: + sync_state = PtpState.Locked + elif sync_state == PtpState.Locked: + sync_state = PtpState.Holdover + elif sync_state == PtpState.Holdover: + sync_state = PtpState.Freerun + else: + sync_state = PtpState.Freerun + else: + new_event, sync_state, new_event_time = \ + ptp_monitor.get_ptp_sync_state() + return new_event, sync_state, new_event_time + + '''announce location''' + + def __publish_os_clock_status(self, forced=False): + holdover_time = float(self.osclocktracker_context['holdover_seconds']) + freq = float(self.osclocktracker_context['poll_freq_seconds']) + sync_state = self.osclocktracker_context.get('sync_state', 'Unknown') + last_event_time = self.osclocktracker_context.get('last_event_time', + time.time()) + lastStatus = {} + + new_event, sync_state, new_event_time = self.__get_os_clock_status( + holdover_time, freq, sync_state, last_event_time) + LOG.info("os_clock_status: state is %s, new_event is %s " + % (sync_state, new_event)) + if new_event or forced: + self.osclocktracker_context_lock.acquire() + self.osclocktracker_context['sync_state'] = sync_state + self.osclocktracker_context['last_event_time'] = new_event_time + self.osclocktracker_context_lock.release() + + LOG.debug("Publish OS Clock Status") + # publish new event in API version v2 format + resource_address = utils.format_resource_address( + self.node_name, constants.SOURCE_SYNC_OS_CLOCK) + lastStatus['os_clock_status'] = { + 'id': uuidutils.generate_uuid(), + 'specversion': constants.SPEC_VERSION, + 'source': constants.SOURCE_SYNC_OS_CLOCK, + 'type': source_type[constants.SOURCE_SYNC_OS_CLOCK], + '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_OS_CLOCK) + self.ptpeventproducer.publish_status( + lastStatus, constants.SOURCE_SYNC_ALL) + + def __publish_overall_sync_status(self, forced=False): + lastStatus = {} + holdover_time = float(self.overalltracker_context['holdover_seconds']) + freq = float(self.overalltracker_context['poll_freq_seconds']) + sync_state = self.overalltracker_context.get('sync_state', 'Unknown') + last_event_time = self.overalltracker_context.get('last_event_time', + time.time()) + + new_event, sync_state, new_event_time = self.__get_overall_sync_state( + holdover_time, freq, sync_state, last_event_time) + LOG.info("overall_sync_state: state is %s, new_event is %s " + % (sync_state, new_event)) + + if new_event or forced: + # Update context + self.overalltracker_context_lock.acquire() + self.overalltracker_context['sync_state'] = sync_state + self.overalltracker_context['last_event_time'] = new_event_time + self.overalltracker_context_lock.release() + + LOG.debug("Publish overall sync status.") + resource_address = utils.format_resource_address( + self.node_name, constants.SOURCE_SYNC_SYNC_STATE) + lastStatus['overall_sync_status'] = { + 'id': uuidutils.generate_uuid(), + 'specversion': constants.SPEC_VERSION, + 'source': constants.SOURCE_SYNC_SYNC_STATE, + 'type': source_type[constants.SOURCE_SYNC_SYNC_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_SYNC_STATE) + self.ptpeventproducer.publish_status( + lastStatus, constants.SOURCE_SYNC_ALL) + + def __publish_gnss_status(self, forced=False): + lastStatus = {} + for gnss in self.observer_list: + holdover_time = float( + self.gnsstracker_context[ + gnss.ts2phc_service_name]['holdover_seconds']) + freq = float(self.gnsstracker_context[ + gnss.ts2phc_service_name]['poll_freq_seconds']) + sync_state = \ + self.gnsstracker_context[gnss.ts2phc_service_name].get( + 'sync_state', 'Unknown') + last_event_time = \ + self.gnsstracker_context[gnss.ts2phc_service_name].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.info("%s gnss_status: state is %s, new_event is %s" + % (gnss.ts2phc_service_name, sync_state, new_event)) + + if new_event or forced: + # update context + self.gnsstracker_context_lock.acquire() + self.gnsstracker_context[ + gnss.ts2phc_service_name]['sync_state'] = sync_state + self.gnsstracker_context[gnss.ts2phc_service_name][ + '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 = utils.format_resource_address( + self.node_name, constants.SOURCE_SYNC_GNSS_SYNC_STATUS) + lastStatus[gnss.ts2phc_service_name] = { + 'id': uuidutils.generate_uuid(), + 'specversion': constants.SPEC_VERSION, + 'source': constants.SOURCE_SYNC_GNSS_SYNC_STATUS, + 'type': source_type[ + constants.SOURCE_SYNC_GNSS_SYNC_STATUS], + '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_GNSS_SYNC_STATUS) + self.ptpeventproducer.publish_status( + lastStatus, constants.SOURCE_SYNC_ALL) + + def __publish_ptpstatus(self, forced=False): + lastStatus = {} + lastClockClassStatus = {} + for ptp_monitor in self.ptp_monitor_list: + holdover_time = float(self.ptptracker_context[ + ptp_monitor.ptp4l_service_name]['holdover_seconds']) + freq = float(self.ptptracker_context[ + ptp_monitor.ptp4l_service_name]['poll_freq_seconds']) + sync_state = \ + self.ptptracker_context[ptp_monitor.ptp4l_service_name].get( + 'sync_state', 'Unknown') + last_event_time = \ + self.ptptracker_context[ptp_monitor.ptp4l_service_name].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, ptp_monitor) + LOG.info("%s PTP sync state: state is %s, new_event is %s" % ( + ptp_monitor.ptp4l_service_name, sync_state, new_event)) + + new_clock_class_event, clock_class, clock_class_event_time = \ + ptp_monitor.get_ptp_clock_class() + LOG.info("%s PTP clock class: clockClass is %s, new_event is %s" + % (ptp_monitor.ptp4l_service_name, clock_class, + new_clock_class_event)) + if new_event or forced: + # update context + self.ptptracker_context_lock.acquire() + self.ptptracker_context[ptp_monitor.ptp4l_service_name][ + 'sync_state'] = sync_state + self.ptptracker_context[ptp_monitor.ptp4l_service_name][ + 'last_event_time'] = new_event_time + + # 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') + lastStatus = {} + # publish new event in API version v2 format + resource_address = utils.format_resource_address( + self.node_name, constants.SOURCE_SYNC_PTP_LOCK_STATE) + lastStatus[ptp_monitor.ptp4l_service_name] = { + '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.ptptracker_context_lock.release() + 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_service_name][ + 'clock_class'] = clock_class + self.ptptracker_context[ptp_monitor.ptp4l_service_name][ + 'last_clock_class_event_time'] \ + = clock_class_event_time + + resource_address = utils.format_resource_address( + self.node_name, constants.SOURCE_SYNC_PTP_CLOCK_CLASS) + + lastClockClassStatus[ptp_monitor.ptp4l_service_name] = { + '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 + } + ] + } + } + self.ptptracker_context_lock.release() + 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) + + +class DaemonControl(object): + + def __init__(self, sqlalchemy_conf_json, daemon_context_json, + process_worker=None): + self.event = mp.Event() + self.daemon_context = json.loads(daemon_context_json) + self.node_name = self.daemon_context['THIS_NODE_NAME'] + if not process_worker: + process_worker = ProcessWorkerDefault + + self.sqlalchemy_conf_json = sqlalchemy_conf_json + self.daemon_context_json = daemon_context_json + self.process_worker = process_worker + + def refresh(self): + self.process_worker(self.event, self.sqlalchemy_conf_json, + self.daemon_context_json) + self.event.set() diff --git a/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/__init__.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_cgu_handler.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_cgu_handler.py similarity index 99% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_cgu_handler.py rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_cgu_handler.py index ddb7e3c..d07fb2d 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_cgu_handler.py +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_cgu_handler.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 Wind River Systems, Inc. +# Copyright (c) 2022-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/mock_cgu_output_logan_beach b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/mock_cgu_output_logan_beach similarity index 100% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/mock_cgu_output_logan_beach rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/mock_cgu_output_logan_beach diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/mock_cgu_output_westport_channel b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/mock_cgu_output_westport_channel similarity index 100% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/mock_cgu_output_westport_channel rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/mock_cgu_output_westport_channel diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/mock_kern.log b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/mock_kern.log similarity index 100% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/mock_kern.log rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/mock_kern.log diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/phc2sys-test.conf b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/phc2sys-test.conf similarity index 100% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/phc2sys-test.conf rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/phc2sys-test.conf diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/ts2phc_invalid.conf b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/ts2phc_invalid.conf similarity index 100% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/ts2phc_invalid.conf rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/ts2phc_invalid.conf diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/ts2phc_valid.conf b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/ts2phc_valid.conf similarity index 100% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/ts2phc_valid.conf rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_input_files/ts2phc_valid.conf diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_os_clock_monitor.py b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_os_clock_monitor.py similarity index 98% rename from notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_os_clock_monitor.py rename to notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_os_clock_monitor.py index 805796b..9de9d01 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_os_clock_monitor.py +++ b/notificationservice-base-v2/docker/ptptrackingfunction/trackingfunctionsdk/tests/test_os_clock_monitor.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 Wind River Systems, Inc. +# Copyright (c) 2022-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # diff --git a/notificationservice-base/centos/notificationservice-base.stable_docker_image b/notificationservice-base/centos/notificationservice-base.stable_docker_image index e028c69..2bd26d6 100644 --- a/notificationservice-base/centos/notificationservice-base.stable_docker_image +++ b/notificationservice-base/centos/notificationservice-base.stable_docker_image @@ -1,4 +1,2 @@ BUILDER=docker -LABEL=notificationservice-base -DOCKER_CONTEXT=../docker -DOCKER_FILE=./Dockerfile \ No newline at end of file +LABEL=notificationservice-base \ No newline at end of file diff --git a/notificationservice-base/docker/ptptrackingfunction/setup.cfg b/notificationservice-base/docker/ptptrackingfunction/setup.cfg index 19c8281..278305c 100644 --- a/notificationservice-base/docker/ptptrackingfunction/setup.cfg +++ b/notificationservice-base/docker/ptptrackingfunction/setup.cfg @@ -1,6 +1,6 @@ -[nosetests] -match=^test -where=ptptrackingfunction -nocapture=1 -cover-package=ptptrackingfunction -cover-erase=1 +[nosetests] +match=^test +where=ptptrackingfunction +nocapture=1 +cover-package=ptptrackingfunction +cover-erase=1 diff --git a/notificationservice-base/docker/ptptrackingfunction/setup.py b/notificationservice-base/docker/ptptrackingfunction/setup.py index ca450f2..f299572 100644 --- a/notificationservice-base/docker/ptptrackingfunction/setup.py +++ b/notificationservice-base/docker/ptptrackingfunction/setup.py @@ -1,28 +1,28 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2021 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -try: - from setuptools import setup, find_packages -except ImportError: - from ez_setup import use_setuptools - use_setuptools() - from setuptools import setup, find_packages - -setup( - name='ptptrackingfunction', - version='0.1', - description='', - author='', - author_email='', - install_requires=[ - "", - ], - test_suite='ptptrackingfunction', - zip_safe=False, - include_package_data=True, - packages=find_packages(exclude=['ez_setup']) -) +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +try: + from setuptools import setup, find_packages +except ImportError: + from ez_setup import use_setuptools + use_setuptools() + from setuptools import setup, find_packages + +setup( + name='ptptrackingfunction', + version='0.1', + description='', + author='', + author_email='', + install_requires=[ + "", + ], + test_suite='ptptrackingfunction', + zip_safe=False, + include_package_data=True, + packages=find_packages(exclude=['ez_setup']) +) diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/__init__.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/__init__.py index fed7dda..bac78cb 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/__init__.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/__init__.py @@ -1,5 +1,5 @@ -# -# Copyright (c) 2021 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/client/base.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/client/base.py index a7fb010..34cc8c7 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/client/base.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/client/base.py @@ -1,113 +1,113 @@ -# -# Copyright (c) 2021 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -import os -import json -import time -import oslo_messaging -from oslo_config import cfg -from trackingfunctionsdk.common.helpers import rpc_helper -from trackingfunctionsdk.model.dto.rpc_endpoint import RpcEndpointInfo - -import logging - -LOG = logging.getLogger(__name__) - -from trackingfunctionsdk.common.helpers import log_helper -log_helper.config_logger(LOG) - -class BrokerClientBase(object): - def __init__(self, broker_name, broker_transport_endpoint): - self.broker_name = broker_name - self.listeners = {} - self.broker_endpoint = RpcEndpointInfo(broker_transport_endpoint) - self.transport = rpc_helper.get_transport(self.broker_endpoint) - LOG.debug("Created Broker client:{0}".format(broker_name)) - - def __del__(self): - self.transport.cleanup() - del self.transport - return - - def __create_listener(self, context): - target = oslo_messaging.Target( - topic=context['topic'], - server=context['server']) - endpoints = context['endpoints'] - server = oslo_messaging.get_rpc_server( - self.transport, target, endpoints, executor=None) - return server - - def _refresh(self): - for topic, servers in self.listeners.items(): - for servername, context in servers.items(): - try: - rpcserver = context.get('rpcserver', None) - isactive = context.get('active', False) - if isactive and not rpcserver: - rpcserver = self.__create_listener(context) - rpcserver.start() - context['rpcserver'] = rpcserver - LOG.debug("Started rpcserver@{0}@{1}".format(context['topic'], context['server'])) - elif not isactive and rpcserver: - rpcserver.stop() - rpcserver.wait() - context.pop('rpcserver') - LOG.debug("Stopped rpcserver@{0}@{1}".format(context['topic'], context['server'])) - except: - LOG.error("Failed to update listener for topic/server:{0}/{1}" - .format(topic, servername)) - continue - - def add_listener(self, topic, server, listener_endpoints=None): - context = self.listeners.get(topic,{}).get(server, {}) - if not context: - context = { - 'endpoints': listener_endpoints, - 'topic': topic, - 'server': server, - 'active': True - } - if not self.listeners.get(topic, None): - self.listeners[topic] = {} - self.listeners[topic][server] = context - else: - context['endpoints'] = listener_endpoints - context['active'] = True - - self._refresh() - - def remove_listener(self, topic, server): - context = self.listeners.get(topic,{}).get(server, {}) - if context: - context['active'] = False - self._refresh() - - def is_listening(self, topic, server): - context = self.listeners.get(topic,{}).get(server, {}) - return context.get('active', False) - - def any_listener(self): - for topic, servers in self.listeners.items(): - for servername, context in servers.items(): - isactive = context.get('active', False) - if isactive: - return True - return False - - def call(self, topic, server, api_name, timeout=2, retry=0, **api_kwargs): - target = oslo_messaging.Target( - topic=topic, server=server, version=self.broker_endpoint.Version, - namespace=self.broker_endpoint.Namespace) - queryclient = oslo_messaging.RPCClient(self.transport, target, timeout = timeout, retry = retry) - return queryclient.call({}, api_name, **api_kwargs) - - def cast(self, topic, api_name, **api_kwargs): - target = oslo_messaging.Target( - topic=topic, fanout=True, version=self.broker_endpoint.Version, - namespace=self.broker_endpoint.Namespace) - queryclient = oslo_messaging.RPCClient(self.transport, target) - queryclient.cast({}, api_name, **api_kwargs) +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import os +import json +import time +import oslo_messaging +from oslo_config import cfg +from trackingfunctionsdk.common.helpers import rpc_helper +from trackingfunctionsdk.model.dto.rpc_endpoint import RpcEndpointInfo + +import logging + +LOG = logging.getLogger(__name__) + +from trackingfunctionsdk.common.helpers import log_helper +log_helper.config_logger(LOG) + +class BrokerClientBase(object): + def __init__(self, broker_name, broker_transport_endpoint): + self.broker_name = broker_name + self.listeners = {} + self.broker_endpoint = RpcEndpointInfo(broker_transport_endpoint) + self.transport = rpc_helper.get_transport(self.broker_endpoint) + LOG.debug("Created Broker client:{0}".format(broker_name)) + + def __del__(self): + self.transport.cleanup() + del self.transport + return + + def __create_listener(self, context): + target = oslo_messaging.Target( + topic=context['topic'], + server=context['server']) + endpoints = context['endpoints'] + server = oslo_messaging.get_rpc_server( + self.transport, target, endpoints, executor=None) + return server + + def _refresh(self): + for topic, servers in self.listeners.items(): + for servername, context in servers.items(): + try: + rpcserver = context.get('rpcserver', None) + isactive = context.get('active', False) + if isactive and not rpcserver: + rpcserver = self.__create_listener(context) + rpcserver.start() + context['rpcserver'] = rpcserver + LOG.debug("Started rpcserver@{0}@{1}".format(context['topic'], context['server'])) + elif not isactive and rpcserver: + rpcserver.stop() + rpcserver.wait() + context.pop('rpcserver') + LOG.debug("Stopped rpcserver@{0}@{1}".format(context['topic'], context['server'])) + except: + LOG.error("Failed to update listener for topic/server:{0}/{1}" + .format(topic, servername)) + continue + + def add_listener(self, topic, server, listener_endpoints=None): + context = self.listeners.get(topic,{}).get(server, {}) + if not context: + context = { + 'endpoints': listener_endpoints, + 'topic': topic, + 'server': server, + 'active': True + } + if not self.listeners.get(topic, None): + self.listeners[topic] = {} + self.listeners[topic][server] = context + else: + context['endpoints'] = listener_endpoints + context['active'] = True + + self._refresh() + + def remove_listener(self, topic, server): + context = self.listeners.get(topic,{}).get(server, {}) + if context: + context['active'] = False + self._refresh() + + def is_listening(self, topic, server): + context = self.listeners.get(topic,{}).get(server, {}) + return context.get('active', False) + + def any_listener(self): + for topic, servers in self.listeners.items(): + for servername, context in servers.items(): + isactive = context.get('active', False) + if isactive: + return True + return False + + def call(self, topic, server, api_name, timeout=2, retry=0, **api_kwargs): + target = oslo_messaging.Target( + topic=topic, server=server, version=self.broker_endpoint.Version, + namespace=self.broker_endpoint.Namespace) + queryclient = oslo_messaging.RPCClient(self.transport, target, timeout = timeout, retry = retry) + return queryclient.call({}, api_name, **api_kwargs) + + def cast(self, topic, api_name, **api_kwargs): + target = oslo_messaging.Target( + topic=topic, fanout=True, version=self.broker_endpoint.Version, + namespace=self.broker_endpoint.Namespace) + queryclient = oslo_messaging.RPCClient(self.transport, target) + queryclient.cast({}, api_name, **api_kwargs) diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/client/ptpeventproducer.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/client/ptpeventproducer.py index 35e5c7d..d18cdd0 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/client/ptpeventproducer.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/client/ptpeventproducer.py @@ -1,203 +1,198 @@ -# -# Copyright (c) 2021-2022 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -import os -import json -import time -import oslo_messaging -from oslo_config import cfg - -from trackingfunctionsdk.client.base import BrokerClientBase - -import logging - -LOG = logging.getLogger(__name__) - -from trackingfunctionsdk.common.helpers import log_helper - -log_helper.config_logger(LOG) - - -class PtpEventProducer(object): - class ListenerEndpoint(object): - target = oslo_messaging.Target(namespace='notification', version='1.0') - - def __init__(self, handler=None): - - self.handler = handler - self.init_time = time.time() - pass - - def QueryStatus(self, ctx, **rpc_kwargs): - LOG.debug("PtpEventProducer QueryStatus called %s" % rpc_kwargs) - if self.handler: - return self.handler.query_status(**rpc_kwargs) - else: - return None - - def TriggerDelivery(self, ctx, **rpc_kwargs): - LOG.debug("PtpEventProducer TriggerDelivery called %s" % rpc_kwargs) - if self.handler: - return self.handler.trigger_delivery(**rpc_kwargs) - else: - return None - - def __init__(self, node_name, local_broker_transport_endpoint, - registration_broker_transport_endpoint=None): - self.Id = id(self) - self.node_name = node_name - self.local_broker_client = BrokerClientBase( - 'LocalPtpEventProducer', local_broker_transport_endpoint) - if registration_broker_transport_endpoint: - self.registration_broker_client = BrokerClientBase( - 'AllPtpEventProducer', registration_broker_transport_endpoint) - else: - self.registration_broker_client = None - return - - def __del__(self): - if self.local_broker_client: - del self.local_broker_client - self.local_broker_client = None - if self.registration_broker_client: - del self.registration_broker_client - self.registration_broker_client = None - return - - def publish_status(self, ptpstatus, retry=3): - result = False - result1 = self.publish_status_local(ptpstatus, - retry) if self.local_broker_client else result - result2 = self.publish_status_all(ptpstatus, - retry) if self.registration_broker_client else result - return result1, result2 - - def publish_status_local(self, ptpstatus, source, retry=3): - if not self.local_broker_client: - return False - topic = '{0}-Event-{1}'.format(source, self.node_name) - server = None - isretrystopped = False - while not isretrystopped: - try: - self.local_broker_client.cast( - topic, 'NotifyStatus', notification=ptpstatus) - LOG.debug("Published ptp status:{0}@Topic:{1}".format(ptpstatus, topic)) - break - except Exception as ex: - LOG.warning("Failed to publish ptp status:{0}@Topic:{1} due to: {2}".format( - ptpstatus, topic, str(ex))) - retry = retry - 1 - isretrystopped = False if retry > 0 else True - - if isretrystopped: - LOG.error("Failed to publish ptp status:{0}@Topic:{1}".format( - ptpstatus, topic)) - return isretrystopped == False - - def publish_status_all(self, ptpstatus, retry=3): - if not self.registration_broker_client: - return False - topic_all = 'PTP-Event-*' - server = None - isretrystopped = False - while not isretrystopped: - try: - self.registration_broker_client.cast( - topic_all, 'NotifyStatus', notification=ptpstatus) - LOG.debug("Published ptp status:{0}@Topic:{1}".format(ptpstatus, topic_all)) - break - except Exception as ex: - LOG.warning("Failed to publish ptp status:{0}@Topic:{1} due to: {2}".format( - ptpstatus, topic_all, str(ex))) - retry = retry - 1 - isretrystopped = False if retry > 0 else True - - if isretrystopped: - LOG.error("Failed to publish ptp status:{0}@Topic:{1}".format( - ptpstatus, topic_all)) - return isretrystopped == False - - def start_status_listener(self, handler=None): - result = False - result1 = self.start_status_listener_local(handler) if self.local_broker_client else result - result2 = self.start_status_listener_all( - handler) if self.registration_broker_client else result - result = result1 and result2 - return result - - def start_status_listener_local(self, handler=None): - if not self.local_broker_client: - return False - - topic = 'PTP-Status' - server = 'PTP-Tracking-{0}'.format(self.node_name) - endpoints = [PtpEventProducer.ListenerEndpoint(handler)] - - self.local_broker_client.add_listener( - topic, server, endpoints) - return True - - def start_status_listener_all(self, handler=None): - if not self.registration_broker_client: - return False - - topic = 'PTP-Status' - server = 'PTP-Tracking-{0}'.format(self.node_name) - endpoints = [PtpEventProducer.ListenerEndpoint(handler)] - - self.registration_broker_client.add_listener( - topic, server, endpoints) - return True - - def stop_status_listener(self): - result = False - result1 = self.stop_status_listener_local() if self.local_broker_client else result - result2 = self.stop_status_listener_all() if self.registration_broker_client else result - result = result1 and result2 - return result - - def stop_status_listener_local(self): - if not self.local_broker_client: - return False - - topic = 'PTP-Status' - server = "PTP-Tracking-{0}".format(self.node_name) - self.local_broker_client.remove_listener( - topic, server) - - def stop_status_listener_all(self): - if not self.registration_broker_client: - return False - - topic = 'PTP-Status' - server = "PTP-Tracking-{0}".format(self.node_name) - self.registration_broker_client.remove_listener( - topic, server) - - def is_listening(self): - result = False - result1 = self.is_listening_local() if self.local_broker_client else result - result2 = self.is_listening_all() if self.registration_broker_client else result - result = result1 and result2 - return result - - def is_listening_local(self): - if not self.local_broker_client: - return False - - topic = 'PTP-Status' - server = "PTP-Tracking-{0}".format(self.node_name) - return self.local_broker_client.is_listening( - topic, server) - - def is_listening_all(self): - if not self.registration_broker_client: - return False - topic = 'PTP-Status' - server = "PTP-Tracking-{0}".format(self.node_name) - return self.registration_broker_client.is_listening( - topic, server) +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import os +import json +import time +import oslo_messaging +from oslo_config import cfg + +from trackingfunctionsdk.client.base import BrokerClientBase + +import logging + +LOG = logging.getLogger(__name__) + +from trackingfunctionsdk.common.helpers import log_helper +log_helper.config_logger(LOG) + +class PtpEventProducer(object): + class ListenerEndpoint(object): + target = oslo_messaging.Target(namespace='notification', version='1.0') + + def __init__(self, handler=None): + + self.handler = handler + self.init_time = time.time() + pass + + def QueryStatus(self, ctx, **rpc_kwargs): + LOG.debug("PtpEventProducer QueryStatus called %s" %rpc_kwargs) + if self.handler: + return self.handler.query_status(**rpc_kwargs) + else: + return None + + def TriggerDelivery(self, ctx, **rpc_kwargs): + LOG.debug ("PtpEventProducer TriggerDelivery called %s" %rpc_kwargs) + if self.handler: + return self.handler.trigger_delivery(**rpc_kwargs) + else: + return None + + def __init__(self, node_name, local_broker_transport_endpoint, + registration_broker_transport_endpoint=None): + self.Id = id(self) + self.node_name = node_name + self.local_broker_client = BrokerClientBase( + 'LocalPtpEventProducer', local_broker_transport_endpoint) + if registration_broker_transport_endpoint: + self.registration_broker_client = BrokerClientBase( + 'AllPtpEventProducer', registration_broker_transport_endpoint) + else: + self.registration_broker_client = None + return + + def __del__(self): + if self.local_broker_client: + del self.local_broker_client + self.local_broker_client = None + if self.registration_broker_client: + del self.registration_broker_client + self.registration_broker_client = None + return + + def publish_status(self, ptpstatus, retry=3): + result = False + result1 = self.publish_status_local(ptpstatus, retry) if self.local_broker_client else result + result2 = self.publish_status_all(ptpstatus, retry) if self.registration_broker_client else result + return result1, result2 + + def publish_status_local(self, ptpstatus, retry=3): + if not self.local_broker_client: + return False + topic='PTP-Event-{0}'.format(self.node_name) + server = None + isretrystopped = False + while not isretrystopped: + try: + self.local_broker_client.cast( + topic, 'NotifyStatus', notification=ptpstatus) + LOG.debug("Published ptp status:{0}@Topic:{1}".format(ptpstatus, topic)) + break + except Exception as ex: + LOG.warning("Failed to publish ptp status:{0}@Topic:{1} due to: {2}".format( + ptpstatus, topic, str(ex))) + retry = retry - 1 + isretrystopped = False if retry > 0 else True + + if isretrystopped: + LOG.error("Failed to publish ptp status:{0}@Topic:{1}".format( + ptpstatus, topic)) + return isretrystopped == False + + def publish_status_all(self, ptpstatus, retry=3): + if not self.registration_broker_client: + return False + topic_all='PTP-Event-*' + server = None + isretrystopped = False + while not isretrystopped: + try: + self.registration_broker_client.cast( + topic_all, 'NotifyStatus', notification=ptpstatus) + LOG.debug("Published ptp status:{0}@Topic:{1}".format(ptpstatus, topic_all)) + break + except Exception as ex: + LOG.warning("Failed to publish ptp status:{0}@Topic:{1} due to: {2}".format( + ptpstatus, topic_all, str(ex))) + retry = retry - 1 + isretrystopped = False if retry > 0 else True + + if isretrystopped: + LOG.error("Failed to publish ptp status:{0}@Topic:{1}".format( + ptpstatus, topic)) + return isretrystopped == False + + def start_status_listener(self, handler=None): + result = False + result1 = self.start_status_listener_local(handler) if self.local_broker_client else result + result2 = self.start_status_listener_all(handler) if self.registration_broker_client else result + result = result1 and result2 + return result + + def start_status_listener_local(self, handler=None): + if not self.local_broker_client: + return False + + topic='PTP-Status' + server='PTP-Tracking-{0}'.format(self.node_name) + endpoints = [PtpEventProducer.ListenerEndpoint(handler)] + + self.local_broker_client.add_listener( + topic, server, endpoints) + return True + + def start_status_listener_all(self, handler=None): + if not self.registration_broker_client: + return False + + topic='PTP-Status' + server='PTP-Tracking-{0}'.format(self.node_name) + endpoints = [PtpEventProducer.ListenerEndpoint(handler)] + + self.registration_broker_client.add_listener( + topic, server, endpoints) + return True + + def stop_status_listener(self): + result = False + result1 = self.stop_status_listener_local() if self.local_broker_client else result + result2 = self.stop_status_listener_all() if self.registration_broker_client else result + result = result1 and result2 + return result + + def stop_status_listener_local(self): + if not self.local_broker_client: + return False + + topic='PTP-Status' + server="PTP-Tracking-{0}".format(self.node_name) + self.local_broker_client.remove_listener( + topic, server) + + def stop_status_listener_all(self): + if not self.registration_broker_client: + return False + + topic='PTP-Status' + server="PTP-Tracking-{0}".format(self.node_name) + self.registration_broker_client.remove_listener( + topic, server) + + def is_listening(self): + result = False + result1 = self.is_listening_local() if self.local_broker_client else result + result2 = self.is_listening_all() if self.registration_broker_client else result + result = result1 and result2 + return result + + def is_listening_local(self): + if not self.local_broker_client: + return False + + topic='PTP-Status' + server="PTP-Tracking-{0}".format(self.node_name) + return self.local_broker_client.is_listening( + topic, server) + + def is_listening_all(self): + if not self.registration_broker_client: + return False + topic='PTP-Status' + server="PTP-Tracking-{0}".format(self.node_name) + return self.registration_broker_client.is_listening( + topic, server) diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/constants.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/constants.py index b3a201e..4f28f3d 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/constants.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/constants.py @@ -1,9 +1,8 @@ # -# Copyright (c) 2021-2022 Wind River Systems, Inc. +# Copyright (c) 2021-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # - from os import path # phc states constants @@ -20,7 +19,6 @@ 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" MASTER_MODE = "master" @@ -28,15 +26,6 @@ TIME_IS_TRACEABLE1 = "1" TIME_IS_TRACEABLE2 = "true" GM_IS_PRESENT = "true" CLOCK_CLASS_VALUE6 = "6" -# ts2phc constants -NMEA_SERIALPORT = "ts2phc.nmea_serialport" -GNSS_PIN = "GNSS-1PPS" -GNSS_LOCKED_HO_ACK = 'locked_ho_ack' -GNSS_LOCKED_HO_ACQ = 'locked_ho_acq' -GNSS_DPLL_0 = "DPLL0" -GNSS_DPLL_1 = "DPLL1" - -UTC_OFFSET = "37" if path.exists('/ptp/linuxptp/ptpinstance'): LINUXPTP_CONFIG_PATH = '/ptp/linuxptp/ptpinstance/' @@ -44,32 +33,3 @@ elif path.exists('/ptp/ptpinstance'): LINUXPTP_CONFIG_PATH = '/ptp/ptpinstance/' else: LINUXPTP_CONFIG_PATH = '/ptp/' -PTP_CONFIG_PATH = LINUXPTP_CONFIG_PATH -PHC2SYS_CONFIG_PATH = LINUXPTP_CONFIG_PATH -TS2PHC_CONFIG_PATH = LINUXPTP_CONFIG_PATH -PHC_CTL_PATH = "/usr/sbin/phc_ctl" -PHC2SYS_DEFAULT_CONFIG = PHC2SYS_CONFIG_PATH + "phc2sys-phc2sys-legacy.conf" - -CLOCK_REALTIME = "CLOCK_REALTIME" - -PHC2SYS_TOLERANCE_LOW = 36999999000 -PHC2SYS_TOLERANCE_HIGH = 37000001000 - -PTP_V1_KEY = "ptp_notification_v1" - -SPEC_VERSION = "1.0" -DATA_VERSION = "1.0" -DATA_TYPE_NOTIFICATION = "notification" -DATA_TYPE_METRIC = "metric" -VALUE_TYPE_ENUMERATION = "enumeration" -VALUE_TYPE_METRIC = "metric" - -SOURCE_SYNC_ALL = '/sync' -SOURCE_SYNC_GNSS_SYNC_STATUS = '/sync/gnss-status/gnss-sync-status' -SOURCE_SYNC_PTP_CLOCK_CLASS = '/sync/ptp-status/clock-class' -SOURCE_SYNC_PTP_LOCK_STATE = '/sync/ptp-status/lock-state' -SOURCE_SYNC_OS_CLOCK = '/sync/sync-status/os-clock-sync-state' -SOURCE_SYNC_SYNC_STATE = '/sync/sync-status/sync-state' -SOURCE_SYNCE_CLOCK_QUALITY = '/sync/synce-status/clock-quality' -SOURCE_SYNCE_LOCK_STATE_EXTENDED = '/sync/synce-status/lock-state-extended' -SOURCE_SYNCE_LOCK_STATE = '/sync/synce-status/lock-state' diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py index 28e2d21..afcc625 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/log_helper.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2021-2022 Wind River Systems, Inc. +# Copyright (c) 2021-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -19,4 +19,4 @@ def config_logger(logger): format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') logger.setLevel(level=os.environ.get("LOGGING_LEVEL", "INFO")) - return logger + return logger \ No newline at end of file diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py index 0e88e0e..a789209 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/ptpsync.py @@ -1,6 +1,6 @@ #! /usr/bin/python3 # -# Copyright (c) 2021-2022 Wind River Systems, Inc. +# Copyright (c) 2021-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -12,24 +12,38 @@ # Sync status provided as: 'Locked', 'Holdover', 'Freerun' # # -import os -import re +import errno, os +import os.path +import sys import subprocess +import datetime import logging from trackingfunctionsdk.common.helpers import constants -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): cwd = os.getcwd() os.chdir(dir) process = subprocess.Popen(args, shell=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = process.communicate() errcode = process.returncode @@ -37,8 +51,7 @@ def run_shell2(dir, ctx, args): return out, err, errcode - -def check_critical_resources(ptp4l_service_name, phc2sys_service_name): +def check_critical_resources(): pmc = False ptp4l = False phc2sys = False @@ -50,12 +63,10 @@ def check_critical_resources(ptp4l_service_name, phc2sys_service_name): ptp4l = True if os.path.isfile('/var/run/phc2sys-%s.pid' % phc2sys_service_name): phc2sys = True - if os.path.isfile(constants.PTP_CONFIG_PATH + - ('ptp4l-%s.conf' % ptp4l_service_name)): + if os.path.isfile('%sptp4l-%s.conf' % (constants.LINUXPTP_CONFIG_PATH, ptp4l_service_name)): ptp4lconf = True return pmc, ptp4l, phc2sys, ptp4lconf - 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 @@ -75,6 +86,8 @@ def check_results(result, total_ptp_keywords, port_count): elif result[constants.GRANDMASTER_IDENTITY] == result[constants.CLOCK_IDENTITY]: local_gm = True LOG.debug("Local node is a GM") +# else: +# local_gm = True for port in range(1, port_count + 1): if result[constants.PORT.format(port)].lower() == constants.SLAVE_MODE: break @@ -90,18 +103,96 @@ def check_results(result, total_ptp_keywords, port_count): sync_state = constants.FREERUN_PHC_STATE return sync_state +def ptpsync(): + result = {} + total_ptp_keywords = 0 + port_count = 0 -def parse_resource_address(resource_address): - # The format of resource address is: - # /{clusterName}/{siteName}(/optional/hierarchy/..)/{nodeName}/{resource} - # Assume no optional hierarchy for now - clusterName = resource_address.split('/')[1] - nodeName = resource_address.split('/')[2] - resource_path = '/' + re.split('[/]', resource_address, 3)[3] - return clusterName, nodeName, resource_path + 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 " + constants.LINUXPTP_CONFIG_PATH + "ptp4l-" + ptp4l_service_name + ".conf " + cmd -def format_resource_address(node_name, resource): - # Return a resource_address - resource_address = '/./' + node_name + resource - return resource_address + 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.now().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.now().timestamp() + else: + new_event = "false" + return new_event, sync_state, event_time diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/rpc_helper.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/rpc_helper.py index 9e45a43..cd97a3d 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/rpc_helper.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/common/helpers/rpc_helper.py @@ -1,24 +1,24 @@ -#coding=utf-8 -# -# Copyright (c) 2021 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -import oslo_messaging -from oslo_config import cfg - - -def setup_client(rpc_endpoint_info, topic, server): - oslo_messaging.set_transport_defaults(rpc_endpoint_info.Exchange) - transport = oslo_messaging.get_rpc_transport(cfg.CONF, url=rpc_endpoint_info.TransportEndpoint) - target = oslo_messaging.Target(topic=topic, - version=rpc_endpoint_info.Version, - server=server, - namespace=rpc_endpoint_info.Namespace) - client = oslo_messaging.RPCClient(transport, target) - return client - -def get_transport(rpc_endpoint_info): - oslo_messaging.set_transport_defaults(rpc_endpoint_info.Exchange) - return oslo_messaging.get_rpc_transport(cfg.CONF, url=rpc_endpoint_info.TransportEndpoint) +#coding=utf-8 +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import oslo_messaging +from oslo_config import cfg + + +def setup_client(rpc_endpoint_info, topic, server): + oslo_messaging.set_transport_defaults(rpc_endpoint_info.Exchange) + transport = oslo_messaging.get_rpc_transport(cfg.CONF, url=rpc_endpoint_info.TransportEndpoint) + target = oslo_messaging.Target(topic=topic, + version=rpc_endpoint_info.Version, + server=server, + namespace=rpc_endpoint_info.Namespace) + client = oslo_messaging.RPCClient(transport, target) + return client + +def get_transport(rpc_endpoint_info): + oslo_messaging.set_transport_defaults(rpc_endpoint_info.Exchange) + return oslo_messaging.get_rpc_transport(cfg.CONF, url=rpc_endpoint_info.TransportEndpoint) diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/__init__.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/__init__.py index fed7dda..bac78cb 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/__init__.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/__init__.py @@ -1,5 +1,5 @@ -# -# Copyright (c) 2021 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstate.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstate.py index 5415ac4..735c1ad 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstate.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstate.py @@ -1,11 +1,11 @@ -#coding=utf-8 - -from wsme import types as wtypes - -EnumPtpState = wtypes.Enum(str, 'Locked', 'Freerun', 'Holdover') - -class PtpState(object): - Locked = "Locked" - Freerun = "Freerun" - Holdover = "Holdover" - +#coding=utf-8 + +from wsme import types as wtypes + +EnumPtpState = wtypes.Enum(str, 'Locked', 'Freerun', 'Holdover') + +class PtpState(object): + Locked = "Locked" + Freerun = "Freerun" + Holdover = "Holdover" + diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstatus.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstatus.py index 0be94fc..b8eed69 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstatus.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/ptpstatus.py @@ -1,24 +1,24 @@ -#coding=utf-8 - -from wsme import types as wtypes -from trackingfunctionsdk.model.dto.resourcetype import EnumResourceType -from trackingfunctionsdk.model.dto.ptpstate import PtpState - -class PtpStatus(wtypes.Base): - EventTimestamp = float - ResourceType = EnumResourceType - EventData_State = PtpState - ResourceQualifier_NodeName = wtypes.text - - def to_dict(self): - d = { - 'EventTimestamp': self.EventTimestamp, - 'ResourceType': self.ResourceType, - 'EventData': { - 'State': self.EventData_State - }, - 'ResourceQualifier': { - 'NodeName': self.ResourceQualifier_NodeName - } - } - return d +#coding=utf-8 + +from wsme import types as wtypes +from trackingfunctionsdk.model.dto.resourcetype import EnumResourceType +from trackingfunctionsdk.model.dto.ptpstate import PtpState + +class PtpStatus(wtypes.Base): + EventTimestamp = float + ResourceType = EnumResourceType + EventData_State = PtpState + ResourceQualifier_NodeName = wtypes.text + + def to_dict(self): + d = { + 'EventTimestamp': self.EventTimestamp, + 'ResourceType': self.ResourceType, + 'EventData': { + 'State': self.EventData_State + }, + 'ResourceQualifier': { + 'NodeName': self.ResourceQualifier_NodeName + } + } + return d diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/resourcetype.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/resourcetype.py index 70b5763..5e80cf3 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/resourcetype.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/resourcetype.py @@ -1,10 +1,9 @@ -#coding=utf-8 - -from wsme import types as wtypes - -EnumResourceType = wtypes.Enum(str, 'PTP', 'FPGA') - -class ResourceType(object): - TypePTP = "PTP" - TypeFPGA = "FPGA" - TypeGNSS = "GNSS" +#coding=utf-8 + +from wsme import types as wtypes + +EnumResourceType = wtypes.Enum(str, 'PTP', 'FPGA') + +class ResourceType(object): + TypePTP = "PTP" + TypeFPGA = "FPGA" diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/rpc_endpoint.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/rpc_endpoint.py index e4dcceb..a955655 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/rpc_endpoint.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/model/dto/rpc_endpoint.py @@ -1,34 +1,34 @@ -#coding=utf-8 - -from wsme import types as wtypes - -RPC_ENDPOINT_BASE = { - 'Version': '1.0', - 'Namespace': 'notification', - 'Exchange': 'notification_exchange', - 'TransportEndpoint': '', - 'Topic': '', - 'Server': '' -} - -class RpcEndpointInfo(wtypes.Base): - TransportEndpoint = wtypes.text - Exchange = wtypes.text - Topic = wtypes.text - Server = wtypes.text - Version = wtypes.text - Namespace = wtypes.text - - def __init__(self, transport_endpoint): - self.endpoint_json = { - 'Version': RPC_ENDPOINT_BASE['Version'], - 'Namespace': RPC_ENDPOINT_BASE['Namespace'], - 'Exchange': RPC_ENDPOINT_BASE['Exchange'], - 'TransportEndpoint': transport_endpoint, - 'Topic': RPC_ENDPOINT_BASE['Topic'], - 'Server': RPC_ENDPOINT_BASE['Server'] - } - super(RpcEndpointInfo, self).__init__(**self.endpoint_json) - - def to_dict(self): - return self.endpoint_json +#coding=utf-8 + +from wsme import types as wtypes + +RPC_ENDPOINT_BASE = { + 'Version': '1.0', + 'Namespace': 'notification', + 'Exchange': 'notification_exchange', + 'TransportEndpoint': '', + 'Topic': '', + 'Server': '' +} + +class RpcEndpointInfo(wtypes.Base): + TransportEndpoint = wtypes.text + Exchange = wtypes.text + Topic = wtypes.text + Server = wtypes.text + Version = wtypes.text + Namespace = wtypes.text + + def __init__(self, transport_endpoint): + self.endpoint_json = { + 'Version': RPC_ENDPOINT_BASE['Version'], + 'Namespace': RPC_ENDPOINT_BASE['Namespace'], + 'Exchange': RPC_ENDPOINT_BASE['Exchange'], + 'TransportEndpoint': transport_endpoint, + 'Topic': RPC_ENDPOINT_BASE['Topic'], + 'Server': RPC_ENDPOINT_BASE['Server'] + } + super(RpcEndpointInfo, self).__init__(**self.endpoint_json) + + def to_dict(self): + return self.endpoint_json diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/__init__.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/__init__.py index fed7dda..bac78cb 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/__init__.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/__init__.py @@ -1,5 +1,5 @@ -# -# Copyright (c) 2021 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# +# +# Copyright (c) 2021-2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# diff --git a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py index 148272f..3bb26a1 100644 --- a/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py +++ b/notificationservice-base/docker/ptptrackingfunction/trackingfunctionsdk/services/daemon.py @@ -1,64 +1,41 @@ # -# Copyright (c) 2021-2022 Wind River Systems, Inc. +# Copyright (c) 2021-2023 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # -import datetime -import json -import logging -import multiprocessing as mp + import os -import threading +import json import time -from oslo_utils import uuidutils +import oslo_messaging +from oslo_config import cfg +import logging + +import multiprocessing as mp +import threading + +from trackingfunctionsdk.common.helpers import rpc_helper +from trackingfunctionsdk.model.dto.rpc_endpoint import RpcEndpointInfo +from trackingfunctionsdk.model.dto.resourcetype import ResourceType +from trackingfunctionsdk.model.dto.ptpstate import PtpState from trackingfunctionsdk.client.ptpeventproducer import PtpEventProducer -from trackingfunctionsdk.common.helpers import constants -from trackingfunctionsdk.common.helpers import ptpsync as utils -from trackingfunctionsdk.common.helpers import log_helper -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 -from trackingfunctionsdk.model.dto.overallclockstate import OverallClockState -from trackingfunctionsdk.model.dto.resourcetype import ResourceType -from trackingfunctionsdk.model.dto.rpc_endpoint import RpcEndpointInfo + +from trackingfunctionsdk.common.helpers import ptpsync as ptpsync LOG = logging.getLogger(__name__) + +from trackingfunctionsdk.common.helpers import log_helper log_helper.config_logger(LOG) -THIS_NODE_NAME = os.environ.get("THIS_NODE_NAME", 'controller-0') +THIS_NODE_NAME = os.environ.get("THIS_NODE_NAME",'controller-0') -# Event source to event type mapping -source_type = { - '/sync/gnss-status/gnss-sync-status': - 'event.sync.gnss-status.gnss-state-change', - '/sync/ptp-status/clock-class': - 'event.sync.ptp-status.ptp-clock-class-change', - '/sync/ptp-status/lock-state': - 'event.sync.ptp-status.ptp-state-change', - '/sync/sync-status/os-clock-sync-state': - 'event.sync.sync-status.os-clock-sync-state-change', - '/sync/sync-status/sync-state': - 'event.sync.sync-status.synchronization-state-change', - '/sync/synce-status/clock-quality': - 'event.sync.synce-status.synce-clock-quality-change', - '/sync/synce-status/lock-state-extended': - 'event.sync.synce-status.synce-state-change-extended', - '/sync/synce-status/lock-state': - 'event.sync.synce-status.synce-state-change', -} '''Entry point of Default Process Worker''' - - -def ProcessWorkerDefault(event, sqlalchemy_conf_json, - broker_transport_endpoint): - worker = PtpWatcherDefault(event, sqlalchemy_conf_json, - broker_transport_endpoint) +def ProcessWorkerDefault(event, sqlalchemy_conf_json, broker_transport_endpoint): + worker = PtpWatcherDefault(event, sqlalchemy_conf_json, broker_transport_endpoint) worker.run() + return class PtpWatcherDefault: @@ -67,201 +44,33 @@ class PtpWatcherDefault: 'poll_freq_seconds': 2 } - DEFAULT_GNSSTRACKER_CONTEXT = { - 'holdover_seconds': 30, - 'poll_freq_seconds': 2 - } - - DEFAULT_OS_CLOCK_TRACKER_CONTEXT = { - 'holdover_seconds': 30, - 'poll_freq_seconds': 2 - } - - DEFAULT_OVERALL_SYNC_TRACKER_CONTEXT = { - 'holdover_seconds': 30, - 'poll_freq_seconds': 2 - } - class PtpRequestHandlerDefault(object): - def __init__(self, watcher, daemon_context): + def __init__(self, watcher): self.watcher = watcher self.init_time = time.time() - self.daemon_context = daemon_context - - def _build_event_response( - self, resource_path, last_event_time, resource_address, - sync_state, value_type=constants.VALUE_TYPE_ENUMERATION): - 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 - lastStatus = { - 'id': uuidutils.generate_uuid(), - 'specversion': constants.SPEC_VERSION, - 'source': resource_path, - 'type': source_type[resource_path], - 'time': last_event_time, - 'data': { - 'version': constants.DATA_VERSION, - 'values': [ - { - 'data_type': data_type, - 'ResourceAddress': resource_address, - 'value_type': value_type, - 'value': sync_state - } - ] - } - } - return lastStatus def query_status(self, **rpc_kwargs): - lastStatus = {} - resource_address = rpc_kwargs.get('ResourceAddress', None) - optional = rpc_kwargs.get('optional', None) - if 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() - if optional and self.watcher.gnsstracker_context.get(optional): - sync_state = \ - self.watcher.gnsstracker_context[optional].get( - 'sync_state', GnssState.Failure_Nofix) - last_event_time = \ - self.watcher.gnsstracker_context[optional].get( - 'last_event_time', time.time()) - lastStatus[optional] = self._build_event_response( - resource_path, last_event_time, resource_address, - sync_state) - elif not optional: - for config in self.daemon_context['GNSS_INSTANCES']: - sync_state = \ - self.watcher.gnsstracker_context[config].get( - 'sync_state', GnssState.Failure_Nofix) - last_event_time = \ - self.watcher.gnsstracker_context[config].get( - 'last_event_time', time.time()) - lastStatus[config] = self._build_event_response( - resource_path, last_event_time, - resource_address, sync_state) - else: - lastStatus = None - self.watcher.gnsstracker_context_lock.release() - elif resource_path == constants.SOURCE_SYNC_PTP_CLOCK_CLASS: - self.watcher.ptptracker_context_lock.acquire() - if optional and self.watcher.ptptracker_context.get(optional): - clock_class = \ - self.watcher.ptptracker_context[optional].get( - 'clock_class', '248') - last_clock_class_event_time = \ - self.watcher.ptptracker_context[optional].get( - 'last_clock_class_event_time', time.time()) - lastStatus[optional] = self._build_event_response( - resource_path, last_clock_class_event_time, - resource_address, clock_class, - constants.VALUE_TYPE_METRIC) - elif not optional: - for config in self.daemon_context['PTP4L_INSTANCES']: - clock_class = \ - self.watcher.ptptracker_context[config].get( - 'clock_class', '248') - last_clock_class_event_time = \ - self.watcher.ptptracker_context[config].get( - 'last_clock_class_event_time', - time.time()) - lastStatus[config] = self._build_event_response( - resource_path, last_clock_class_event_time, - resource_address, clock_class, - constants.VALUE_TYPE_METRIC) - else: - lastStatus = None - self.watcher.ptptracker_context_lock.release() - elif resource_path == constants.SOURCE_SYNC_PTP_LOCK_STATE: - self.watcher.ptptracker_context_lock.acquire() - if optional and self.watcher.ptptracker_context.get(optional): - sync_state = \ - self.watcher.ptptracker_context[optional].get( - 'sync_state', PtpState.Freerun) - last_event_time = \ - self.watcher.ptptracker_context[optional].get( - 'last_event_time', time.time()) - lastStatus[optional] = self._build_event_response( - resource_path, last_event_time, resource_address, - sync_state) - elif not optional: - for config in self.daemon_context['PTP4L_INSTANCES']: - sync_state = \ - self.watcher.ptptracker_context[config].get( - 'sync_state', PtpState.Freerun) - last_event_time = \ - self.watcher.ptptracker_context[config].get( - 'last_event_time', time.time()) - lastStatus[config] = self._build_event_response( - resource_path, last_event_time, - resource_address, sync_state) - else: - lastStatus = None - self.watcher.ptptracker_context_lock.release() - - 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()) - self.watcher.osclocktracker_context_lock.release() - lastStatus['os_clock_status'] = 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()) - self.watcher.overalltracker_context_lock.release() - lastStatus['overall_sync_status'] = \ - self._build_event_response( - resource_path, last_event_time, resource_address, - sync_state) - LOG.debug("query_status: {}".format(lastStatus)) - else: - # Request is for PTP v1 notification - # PTP v1 only supports single instance ptp - instance = self.daemon_context['PTP4L_INSTANCES'][0] - if len(self.daemon_context['PTP4L_INSTANCES']) > 1: - LOG.warning("Multiple ptp4l instances configured, " - "retrieving status for %s" % instance) - self.watcher.ptptracker_context_lock.acquire() - sync_state = self.watcher.ptptracker_context[instance].get( - 'sync_state', PtpState.Freerun) - last_event_time = \ - self.watcher.ptptracker_context[instance].get( - 'last_event_time', time.time()) - lastStatus[constants.PTP_V1_KEY] = { - 'ResourceType': ResourceType.TypePTP, - 'EventData': { - 'State': sync_state - }, - 'ResourceQualifier': { - 'NodeName': self.watcher.node_name - }, - 'EventTimestamp': last_event_time - } - self.watcher.ptptracker_context_lock.release() - LOG.warning("query_status PTP v1: {}".format(lastStatus)) + 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()) + self.watcher.ptptracker_context_lock.release() + lastStatus = { + 'ResourceType': ResourceType.TypePTP, + 'EventData': { + 'State': sync_state + }, + 'ResourceQualifier': { + 'NodeName': self.watcher.node_name + }, + 'EventTimestamp': last_event_time + } return lastStatus def trigger_delivery(self, **rpc_kwargs): self.watcher.forced_publishing = True self.watcher.signal_ptp_event() + pass def __init__(self, event, sqlalchemy_conf_json, daemon_context_json): self.sqlalchemy_conf = json.loads(sqlalchemy_conf_json) @@ -269,233 +78,75 @@ class PtpWatcherDefault: self.init_time = time.time() self.daemon_context = json.loads(daemon_context_json) - - # PTP Context - self.ptptracker_context = {} - for config in self.daemon_context['PTP4L_INSTANCES']: - self.ptptracker_context[config] = \ - PtpWatcherDefault.DEFAULT_PTPTRACKER_CONTEXT.copy() - self.ptptracker_context[config]['sync_state'] = PtpState.Freerun - self.ptptracker_context[config]['last_event_time'] = self.init_time - self.ptptracker_context[config]['holdover_seconds'] = \ - os.environ.get("PTP_HOLDOVER_SECONDS", 30) - self.ptptracker_context[config]['poll_freq_seconds'] = \ - os.environ.get("CONTROL_TIMEOUT", 2) - self.ptp_device_simulated = \ - "true" == self.ptptracker_context[config].get( - 'device_simulated', "False") + 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() - LOG.debug("ptptracker_context: %s" % self.ptptracker_context) - # GNSS Context - self.gnsstracker_context = {} - for config in self.daemon_context['GNSS_INSTANCES']: - self.gnsstracker_context[config] = \ - PtpWatcherDefault.DEFAULT_GNSSTRACKER_CONTEXT.copy() - self.gnsstracker_context[config]['sync_state'] = \ - GnssState.Failure_Nofix - self.gnsstracker_context[config]['last_event_time'] = \ - self.init_time - self.gnsstracker_context[config]['holdover_seconds'] = \ - os.environ.get("GNSS_HOLDOVER_SECONDS", 30) - self.gnsstracker_context[config]['poll_freq_seconds'] = \ - os.environ.get("CONTROL_TIMEOUT", 2) - self.gnsstracker_context_lock = threading.Lock() - LOG.debug("gnsstracker_context: %s" % self.gnsstracker_context) + self.ptp_device_simulated = "true" == self.ptptracker_context.get('device_simulated', "False").lower() - # OS Clock Context - self.osclocktracker_context = {} - self.osclocktracker_context = \ - PtpWatcherDefault.DEFAULT_OS_CLOCK_TRACKER_CONTEXT.copy() - self.osclocktracker_context['sync_state'] = OsClockState.Freerun - self.osclocktracker_context['last_event_time'] = self.init_time - self.osclocktracker_context['holdover_seconds'] = \ - os.environ.get("OS_CLOCK_HOLDOVER_SECONDS", 30) - self.osclocktracker_context['poll_freq_seconds'] = \ - os.environ.get("CONTROL_TIMEOUT", 2) - self.osclocktracker_context_lock = threading.Lock() - - # Overall Sync Context - self.overalltracker_context = {} - self.overalltracker_context = \ - PtpWatcherDefault.DEFAULT_OVERALL_SYNC_TRACKER_CONTEXT.copy() - self.overalltracker_context['sync_state'] = OverallClockState.Freerun - self.overalltracker_context['last_event_time'] = self.init_time - self.overalltracker_context['holdover_seconds'] = \ - os.environ.get("OVERALL_HOLDOVER_SECONDS", 30) - self.overalltracker_context['poll_freq_seconds'] = \ - os.environ.get("CONTROL_TIMEOUT", 2) - self.overalltracker_context_lock = threading.Lock() - - self.event_timeout = float(os.environ.get('CONTROL_TIMEOUT', 2)) + self.event_timeout = float(self.ptptracker_context['poll_freq_seconds']) self.node_name = self.daemon_context['THIS_NODE_NAME'] + self.namespace = self.daemon_context.get('THIS_NAMESPACE', 'notification') - self.namespace = self.daemon_context.get( - 'THIS_NAMESPACE', 'notification') + broker_transport_endpoint = self.daemon_context['NOTIFICATION_TRANSPORT_ENDPOINT'] - broker_transport_endpoint = \ - self.daemon_context['NOTIFICATION_TRANSPORT_ENDPOINT'] - - registration_transport_endpoint = \ - self.daemon_context['REGISTRATION_TRANSPORT_ENDPOINT'] + registration_transport_endpoint = self.daemon_context['REGISTRATION_TRANSPORT_ENDPOINT'] self.broker_endpoint = RpcEndpointInfo(broker_transport_endpoint) - self.registration_broker_endpoint = \ - RpcEndpointInfo(registration_transport_endpoint) + self.registration_broker_endpoint = RpcEndpointInfo(registration_transport_endpoint) self.ptpeventproducer = PtpEventProducer( self.node_name, self.broker_endpoint.TransportEndpoint, self.registration_broker_endpoint.TransportEndpoint) - self.__ptprequest_handler = \ - PtpWatcherDefault.PtpRequestHandlerDefault( - self, self.daemon_context) - - # 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.observer_list = [ - GnssMonitor(i) for i in self.daemon_context['GNSS_CONFIGS']] - - # Setup OS Clock monitor - 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'], - self.daemon_context['PHC2SYS_SERVICE_NAME']) - for config in self.daemon_context['PTP4L_INSTANCES']] + self.__ptprequest_handler = PtpWatcherDefault.PtpRequestHandlerDefault(self) + self.forced_publishing = False def signal_ptp_event(self): if self.event: self.event.set() else: LOG.warning("Unable to assert ptp event") + pass def run(self): # start location listener self.__start_listener() - while True: - # announce the location + # annouce the location forced = self.forced_publishing self.forced_publishing = False - 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_overall_sync_status(forced) + self.__publish_ptpstatus(forced) if self.event.wait(self.event_timeout): LOG.debug("daemon control event is asserted") self.event.clear() else: LOG.debug("daemon control event is timeout") + pass continue self.__stop_listener() '''Start listener to answer querying from clients''' - def __start_listener(self): LOG.debug("start listener to answer location querying") self.ptpeventproducer.start_status_listener( self.__ptprequest_handler - ) + ) + return def __stop_listener(self): LOG.debug("stop listener to answer location querying") self.ptpeventproducer.stop_status_listener(self.location_info) + return - def __get_gnss_status(self, holdover_time, freq, sync_state, - last_event_time, gnss_monitor): - new_event, sync_state, new_event_time = gnss_monitor.get_gnss_status( - holdover_time, freq, sync_state, last_event_time) - LOG.debug("Getting GNSS status.") - return new_event, sync_state, new_event_time - - def __get_os_clock_status(self, holdover_time, freq, sync_state, - last_event_time): - new_event, sync_state, new_event_time = \ - self.os_clock_monitor.os_clock_status( - holdover_time, freq, sync_state, last_event_time) - LOG.debug("Getting os clock status.") - return new_event, sync_state, new_event_time - - def __get_overall_sync_state(self, holdover_time, freq, sync_state, - last_event_time): + def __get_ptp_status(self, holdover_time, freq, sync_state, last_event_time): new_event = False new_event_time = last_event_time - previous_sync_state = sync_state - current_time = datetime.datetime.utcnow().timestamp() - time_in_holdover = None - if previous_sync_state == constants.HOLDOVER_PHC_STATE: - time_in_holdover = round(current_time - last_event_time) - max_holdover_time = (holdover_time - freq * 2) - gnss_state = None - os_clock_state = None - ptp_state = None - - LOG.debug("Getting overall sync state.") - for gnss in self.observer_list: - if gnss._state == constants.UNKNOWN_PHC_STATE or \ - gnss._state == GnssState.Failure_Nofix: - gnss_state = GnssState.Failure_Nofix - elif gnss._state == GnssState.Synchronized and \ - gnss_state != GnssState.Failure_Nofix: - gnss_state = GnssState.Synchronized - - for ptp4l in self.ptp_monitor_list: - _, read_state, _ = ptp4l.get_ptp_sync_state() - if read_state == PtpState.Holdover or \ - read_state == PtpState.Freerun or \ - read_state == constants.UNKNOWN_PHC_STATE: - ptp_state = PtpState.Freerun - elif read_state == PtpState.Locked and \ - ptp_state != PtpState.Freerun: - ptp_state = PtpState.Locked - - os_clock_state = self.os_clock_monitor.get_os_clock_state() - - if gnss_state is GnssState.Failure_Nofix or \ - os_clock_state is OsClockState.Freerun or \ - ptp_state is PtpState.Freerun: - sync_state = OverallClockState.Freerun - else: - sync_state = OverallClockState.Locked - - if sync_state == OverallClockState.Freerun: - if previous_sync_state in [ - constants.UNKNOWN_PHC_STATE, - constants.FREERUN_PHC_STATE]: - sync_state = OverallClockState.Freerun - elif previous_sync_state == constants.LOCKED_PHC_STATE: - sync_state = OverallClockState.Holdover - elif previous_sync_state == constants.HOLDOVER_PHC_STATE and \ - time_in_holdover < max_holdover_time: - LOG.debug("Overall sync: Time in holdover is %s " - "Max time in holdover is %s" - % (time_in_holdover, max_holdover_time)) - sync_state = OverallClockState.Holdover - else: - sync_state = OverallClockState.Freerun - - if sync_state != previous_sync_state: - new_event = True - 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, ptp_monitor): - new_event = False - new_event_time = last_event_time - ptp_monitor.set_ptp_sync_state() if self.ptp_device_simulated: now = time.time() timediff = now - last_event_time @@ -511,279 +162,46 @@ class PtpWatcherDefault: else: sync_state = PtpState.Freerun else: - new_event, sync_state, new_event_time = \ - ptp_monitor.get_ptp_sync_state() + new_event, sync_state, new_event_time = ptpsync.ptp_status( + holdover_time, freq, sync_state, last_event_time) return new_event, sync_state, new_event_time '''announce location''' - - def __publish_os_clock_status(self, forced=False): - holdover_time = float(self.osclocktracker_context['holdover_seconds']) - freq = float(self.osclocktracker_context['poll_freq_seconds']) - sync_state = self.osclocktracker_context.get('sync_state', 'Unknown') - last_event_time = self.osclocktracker_context.get('last_event_time', - time.time()) - lastStatus = {} - - new_event, sync_state, new_event_time = self.__get_os_clock_status( - holdover_time, freq, sync_state, last_event_time) - LOG.info("os_clock_status: state is %s, new_event is %s " - % (sync_state, new_event)) - if new_event or forced: - self.osclocktracker_context_lock.acquire() - self.osclocktracker_context['sync_state'] = sync_state - self.osclocktracker_context['last_event_time'] = new_event_time - self.osclocktracker_context_lock.release() - - LOG.debug("Publish OS Clock Status") - # publish new event in API version v2 format - resource_address = utils.format_resource_address( - self.node_name, constants.SOURCE_SYNC_OS_CLOCK) - lastStatus['os_clock_status'] = { - 'id': uuidutils.generate_uuid(), - 'specversion': constants.SPEC_VERSION, - 'source': constants.SOURCE_SYNC_OS_CLOCK, - 'type': source_type[constants.SOURCE_SYNC_OS_CLOCK], - '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_OS_CLOCK) - self.ptpeventproducer.publish_status( - lastStatus, constants.SOURCE_SYNC_ALL) - - def __publish_overall_sync_status(self, forced=False): - lastStatus = {} - holdover_time = float(self.overalltracker_context['holdover_seconds']) - freq = float(self.overalltracker_context['poll_freq_seconds']) - sync_state = self.overalltracker_context.get('sync_state', 'Unknown') - last_event_time = self.overalltracker_context.get('last_event_time', - time.time()) - - new_event, sync_state, new_event_time = self.__get_overall_sync_state( - holdover_time, freq, sync_state, last_event_time) - LOG.info("overall_sync_state: state is %s, new_event is %s " - % (sync_state, new_event)) - - if new_event or forced: - # Update context - self.overalltracker_context_lock.acquire() - self.overalltracker_context['sync_state'] = sync_state - self.overalltracker_context['last_event_time'] = new_event_time - self.overalltracker_context_lock.release() - - LOG.debug("Publish overall sync status.") - resource_address = utils.format_resource_address( - self.node_name, constants.SOURCE_SYNC_SYNC_STATE) - lastStatus['overall_sync_status'] = { - 'id': uuidutils.generate_uuid(), - 'specversion': constants.SPEC_VERSION, - 'source': constants.SOURCE_SYNC_SYNC_STATE, - 'type': source_type[constants.SOURCE_SYNC_SYNC_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_SYNC_STATE) - self.ptpeventproducer.publish_status( - lastStatus, constants.SOURCE_SYNC_ALL) - - def __publish_gnss_status(self, forced=False): - lastStatus = {} - for gnss in self.observer_list: - holdover_time = float( - self.gnsstracker_context[ - gnss.ts2phc_service_name]['holdover_seconds']) - freq = float(self.gnsstracker_context[ - gnss.ts2phc_service_name]['poll_freq_seconds']) - sync_state = \ - self.gnsstracker_context[gnss.ts2phc_service_name].get( - 'sync_state', 'Unknown') - last_event_time = \ - self.gnsstracker_context[gnss.ts2phc_service_name].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.info("%s gnss_status: state is %s, new_event is %s" - % (gnss.ts2phc_service_name, sync_state, new_event)) - - if new_event or forced: - # update context - self.gnsstracker_context_lock.acquire() - self.gnsstracker_context[ - gnss.ts2phc_service_name]['sync_state'] = sync_state - self.gnsstracker_context[gnss.ts2phc_service_name][ - '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 = utils.format_resource_address( - self.node_name, constants.SOURCE_SYNC_GNSS_SYNC_STATUS) - lastStatus[gnss.ts2phc_service_name] = { - 'id': uuidutils.generate_uuid(), - 'specversion': constants.SPEC_VERSION, - 'source': constants.SOURCE_SYNC_GNSS_SYNC_STATUS, - 'type': source_type[ - constants.SOURCE_SYNC_GNSS_SYNC_STATUS], - '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_GNSS_SYNC_STATUS) - self.ptpeventproducer.publish_status( - lastStatus, constants.SOURCE_SYNC_ALL) - def __publish_ptpstatus(self, forced=False): - lastStatus = {} - lastClockClassStatus = {} - for ptp_monitor in self.ptp_monitor_list: - holdover_time = float(self.ptptracker_context[ - ptp_monitor.ptp4l_service_name]['holdover_seconds']) - freq = float(self.ptptracker_context[ - ptp_monitor.ptp4l_service_name]['poll_freq_seconds']) - sync_state = \ - self.ptptracker_context[ptp_monitor.ptp4l_service_name].get( - 'sync_state', 'Unknown') - last_event_time = \ - self.ptptracker_context[ptp_monitor.ptp4l_service_name].get( - 'last_event_time', time.time()) + 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, ptp_monitor) - LOG.info("%s PTP sync state: state is %s, new_event is %s" % ( - ptp_monitor.ptp4l_service_name, sync_state, new_event)) + new_event, sync_state, new_event_time = self.__get_ptp_status( + holdover_time, freq, sync_state, last_event_time) - new_clock_class_event, clock_class, clock_class_event_time = \ - ptp_monitor.get_ptp_clock_class() - LOG.info("%s PTP clock class: clockClass is %s, new_event is %s" - % (ptp_monitor.ptp4l_service_name, clock_class, - new_clock_class_event)) - if new_event or forced: - # update context - self.ptptracker_context_lock.acquire() - self.ptptracker_context[ptp_monitor.ptp4l_service_name][ - 'sync_state'] = sync_state - self.ptptracker_context[ptp_monitor.ptp4l_service_name][ - 'last_event_time'] = new_event_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() - # 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') - lastStatus = {} - # publish new event in API version v2 format - resource_address = utils.format_resource_address( - self.node_name, constants.SOURCE_SYNC_PTP_LOCK_STATE) - lastStatus[ptp_monitor.ptp4l_service_name] = { - '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.ptptracker_context_lock.release() - 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_service_name][ - 'clock_class'] = clock_class - self.ptptracker_context[ptp_monitor.ptp4l_service_name][ - 'last_clock_class_event_time'] \ - = clock_class_event_time - - resource_address = utils.format_resource_address( - self.node_name, constants.SOURCE_SYNC_PTP_CLOCK_CLASS) - - lastClockClassStatus[ptp_monitor.ptp4l_service_name] = { - '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 - } - ] - } - } - self.ptptracker_context_lock.release() - 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) + # 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) + return class DaemonControl(object): - def __init__(self, sqlalchemy_conf_json, daemon_context_json, - process_worker=None): + def __init__(self, sqlalchemy_conf_json, daemon_context_json, process_worker = None): self.event = mp.Event() self.daemon_context = json.loads(daemon_context_json) self.node_name = self.daemon_context['THIS_NODE_NAME'] @@ -793,8 +211,8 @@ class DaemonControl(object): self.sqlalchemy_conf_json = sqlalchemy_conf_json self.daemon_context_json = daemon_context_json self.process_worker = process_worker + return def refresh(self): - self.process_worker(self.event, self.sqlalchemy_conf_json, - self.daemon_context_json) + self.process_worker(self.event, self.sqlalchemy_conf_json, self.daemon_context_json) self.event.set()