
PTP Notification deploys two separate rabbitmq instances: registration and notification-broker, with a default user/password of admin/admin. This commit enables PTP notification to function with an user-defined name, password and also AMQP port for both instances. TEST PLAN: PASS: PTP pull/push notifications with default notification/registration user/password/port (without helm override for these) PASS: PTP pull/push notifications with changing notification/registration user/password/port (with helm overide for these) system helm-override-update ptp-notification ptp-notification \ notification --values ~/notification-override.yaml registration: endpoint: port: 5673 user: admin-reg pass: Registration1234 notification: endpoint: port: 5674 user: admin-noti pass: Notification1234 Story: 2011332 Task: 51620 Signed-off-by: Tara Nath Subedi <tara.subedi@windriver.com> Change-Id: Id98c2ae68a1adb5b17f1a336ac609cd57fe3a026
356 lines
15 KiB
Python
356 lines
15 KiB
Python
#
|
|
# Copyright (c) 2021-2025 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
|
|
import ipaddress
|
|
import json
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
import kombu
|
|
import oslo_messaging
|
|
import requests
|
|
from notificationclientsdk.client.notificationservice import \
|
|
NotificationServiceClient
|
|
from notificationclientsdk.common.helpers import (constants, log_helper,
|
|
subscription_helper)
|
|
from notificationclientsdk.common.helpers.nodeinfo_helper import NodeInfoHelper
|
|
from notificationclientsdk.exception import client_exception
|
|
from notificationclientsdk.model.dto.resourcetype import ResourceType
|
|
from notificationclientsdk.model.dto.subscription import (SubscriptionInfoV1,
|
|
SubscriptionInfoV2)
|
|
from notificationclientsdk.model.orm.subscription import \
|
|
Subscription as SubscriptionOrm
|
|
from notificationclientsdk.repository.node_repo import NodeRepo
|
|
from notificationclientsdk.repository.subscription_repo import SubscriptionRepo
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
log_helper.config_logger(LOG)
|
|
|
|
|
|
class PtpService(object):
|
|
|
|
def __init__(self, daemon_control):
|
|
self.daemon_control = daemon_control
|
|
self.locationservice_client = daemon_control.locationservice_client
|
|
self.subscription_repo = SubscriptionRepo(autocommit=False)
|
|
|
|
def __del__(self):
|
|
del self.subscription_repo
|
|
self.locationservice_client = None
|
|
|
|
def __query_locationinfo(self, broker_name, timeout=5, retry=2):
|
|
try:
|
|
location_info = \
|
|
self.locationservice_client.query_location(broker_name,
|
|
timeout, retry)
|
|
LOG.debug("Pulled location info@{0}:{1}".format(
|
|
broker_name, location_info))
|
|
return location_info
|
|
except Exception as ex:
|
|
LOG.warning("Failed to query location of node:{0} "
|
|
"due to: {1}".format(broker_name, str(ex)))
|
|
raise client_exception.NodeNotAvailable(broker_name)
|
|
|
|
def __get_node_info(self, default_node_name, timeout=5, retry=2):
|
|
try:
|
|
nodeinfo_repo = NodeRepo(autocommit=False)
|
|
nodeinfo = nodeinfo_repo.get_one(Status=1,
|
|
NodeName=default_node_name)
|
|
broker_pod_ip = None
|
|
supported_resource_types = []
|
|
|
|
if nodeinfo:
|
|
broker_pod_ip = nodeinfo.PodIP
|
|
supported_resource_types = json.loads(
|
|
nodeinfo.ResourceTypes or '[]')
|
|
|
|
if not broker_pod_ip:
|
|
# try to query the broker ip
|
|
location_info = self.__query_locationinfo(
|
|
default_node_name, timeout, retry)
|
|
broker_pod_ip = location_info.get("PodIP", None)
|
|
supported_resource_types = location_info.get(
|
|
"ResourceTypes", [])
|
|
|
|
return broker_pod_ip, supported_resource_types
|
|
finally:
|
|
del nodeinfo_repo
|
|
|
|
def query(self, broker_name, resource_address=None, optional=None):
|
|
default_node_name = NodeInfoHelper.default_node_name(broker_name)
|
|
broker_pod_ip, supported_resource_types = self.__get_node_info(
|
|
default_node_name)
|
|
|
|
if not broker_pod_ip:
|
|
LOG.warning("Node {0} is not available yet".format(
|
|
default_node_name))
|
|
raise client_exception.NodeNotAvailable(broker_name)
|
|
|
|
if ResourceType.TypePTP not in supported_resource_types:
|
|
LOG.warning("Resource {0}@{1} is not available yet".format(
|
|
ResourceType.TypePTP, default_node_name))
|
|
raise client_exception.ResourceNotAvailable(broker_name,
|
|
ResourceType.TypePTP)
|
|
|
|
return self._query(default_node_name, broker_pod_ip,
|
|
resource_address, optional)
|
|
|
|
def _query(self, broker_name, broker_pod_ip, resource_address=None,
|
|
optional=None):
|
|
try:
|
|
check_pod_ip = ipaddress.ip_address(broker_pod_ip)
|
|
if check_pod_ip.version == 4:
|
|
broker_host = "{0}".format(broker_pod_ip)
|
|
else: #IPv6
|
|
broker_host = "[{0}]".format(broker_pod_ip)
|
|
except ValueError as err:
|
|
LOG.error("%s: broker_pod_ip %s is not a valid address" % (err, broker_pod_ip))
|
|
raise client_exception.InvalidEndpoint(broker_name)
|
|
|
|
broker_transport_endpoint = "rabbit://{0}:{1}@{2}:{3}".format(
|
|
self.daemon_control.daemon_context['NOTIFICATION_BROKER_USER'],
|
|
self.daemon_control.daemon_context['NOTIFICATION_BROKER_PASS'],
|
|
broker_host,
|
|
self.daemon_control.daemon_context['NOTIFICATION_BROKER_PORT'])
|
|
notificationservice_client = None
|
|
try:
|
|
notificationservice_client = NotificationServiceClient(
|
|
broker_name, broker_transport_endpoint, broker_pod_ip)
|
|
resource_status = notificationservice_client.query_resource_status(
|
|
ResourceType.TypePTP, timeout=5, retry=10,
|
|
resource_address=resource_address, optional=optional)
|
|
return resource_status
|
|
except oslo_messaging.exceptions.MessagingTimeout as ex:
|
|
LOG.warning("ptp status is not available "
|
|
"@node {0} due to {1}".format(broker_name, str(ex)))
|
|
raise client_exception.ResourceNotAvailable(broker_name,
|
|
ResourceType.TypePTP)
|
|
except kombu.exceptions.OperationalError:
|
|
LOG.warning("Node {0} is unreachable yet".format(broker_name))
|
|
raise client_exception.NodeNotAvailable(broker_name)
|
|
finally:
|
|
if notificationservice_client:
|
|
notificationservice_client.cleanup()
|
|
del notificationservice_client
|
|
|
|
def _match_resource_address(self, resource_address_a, resource_address_b):
|
|
|
|
clustername_a, nodename_a, resource_path_a, _, _ = \
|
|
subscription_helper.parse_resource_address(
|
|
resource_address_a)
|
|
|
|
clustername_b, nodename_b, resource_path_b, _, _ = \
|
|
subscription_helper.parse_resource_address(
|
|
resource_address_b)
|
|
|
|
# Compare cluster names
|
|
if clustername_a != clustername_b:
|
|
return False, "clusterName {0} is different from {1}".format(
|
|
clustername_a, clustername_b)
|
|
|
|
# Compare node names
|
|
# If one of them is '*' skip comparison
|
|
if nodename_a != constants.WILDCARD_ALL_NODES and \
|
|
nodename_b != constants.WILDCARD_ALL_NODES:
|
|
|
|
# If one of the nodename is '.' replace by
|
|
# current node.
|
|
if nodename_a == constants.WILDCARD_CURRENT_NODE:
|
|
nodename_a = self.daemon_control.get_residing_nodename()
|
|
if nodename_b == constants.WILDCARD_CURRENT_NODE:
|
|
nodename_b = self.daemon_control.get_residing_nodename()
|
|
|
|
if nodename_a != nodename_b:
|
|
return False, "nodeName {0} is different from {1}".format(
|
|
nodename_a, nodename_b)
|
|
|
|
# Compare resource path
|
|
if resource_path_a == resource_path_b:
|
|
return True, "resourceAddress {0} is equal to {1}".format(
|
|
resource_address_a, resource_address_b)
|
|
|
|
if resource_path_a.startswith(resource_path_b):
|
|
return True, "resourceAddress {1} contains {0}".format(
|
|
resource_address_a, resource_address_b)
|
|
|
|
if resource_path_b.startswith(resource_path_a):
|
|
return True, "resourceAddress {0} contains {1}".format(
|
|
resource_address_a, resource_address_b)
|
|
|
|
return False, "resourceAddress {0} is different from {1}".format(
|
|
resource_address_a, resource_address_b)
|
|
|
|
|
|
def add_subscription(self, subscription_dto):
|
|
resource_address = None
|
|
if hasattr(subscription_dto, 'ResourceAddress'):
|
|
version = 2
|
|
endpoint = subscription_dto.EndpointUri
|
|
resource_address = subscription_dto.ResourceAddress
|
|
|
|
LOG.debug('Looking for existing subscription for EndpointUri %s '
|
|
'ResourceAddress %s' % (endpoint, resource_address))
|
|
|
|
entry = self.subscription_repo.get_one(
|
|
EndpointUri=endpoint,
|
|
ResourceAddress=resource_address)
|
|
|
|
# Did not find matched duplicated, but needs to look for other
|
|
# cases...
|
|
if entry is None:
|
|
|
|
subscriptions = self.subscription_repo.get(
|
|
EndpointUri=endpoint)
|
|
|
|
for subscription in subscriptions:
|
|
match, message = self._match_resource_address(
|
|
subscription.ResourceAddress, resource_address)
|
|
|
|
if match:
|
|
entry = subscription
|
|
LOG.debug(message)
|
|
break
|
|
|
|
if entry is not None:
|
|
LOG.debug('Found existing v2 entry in subscription repo')
|
|
subscriptioninfo = {
|
|
'SubscriptionId': entry.SubscriptionId,
|
|
'UriLocation': entry.UriLocation,
|
|
'EndpointUri': entry.EndpointUri,
|
|
'ResourceAddress': entry.ResourceAddress
|
|
}
|
|
raise client_exception.SubscriptionAlreadyExists(
|
|
subscriptioninfo)
|
|
|
|
_, nodename, _, _, _ = subscription_helper.parse_resource_address(
|
|
resource_address)
|
|
|
|
if nodename == constants.WILDCARD_ALL_NODES:
|
|
broker_names = self.daemon_control.list_of_service_nodenames()
|
|
else:
|
|
broker_names = [nodename]
|
|
|
|
elif hasattr(subscription_dto, 'ResourceType'):
|
|
version = 1
|
|
|
|
resource_qualifier_dto = \
|
|
subscription_dto.ResourceQualifier.to_dict()
|
|
LOG.debug('Looking for existing subscription for EndpointUri %s '
|
|
'ResourceQualifier %s' % (subscription_dto.EndpointUri,
|
|
resource_qualifier_dto))
|
|
entries = self.subscription_repo.get(
|
|
EndpointUri=subscription_dto.EndpointUri)
|
|
for entry in entries:
|
|
resource_qualifier_json = entry.ResourceQualifierJson or '{}'
|
|
resource_qualifier_repo = json.loads(resource_qualifier_json)
|
|
if resource_qualifier_dto == resource_qualifier_repo:
|
|
LOG.debug('Found existing v1 entry in subscription repo')
|
|
raise client_exception.ServiceError(409)
|
|
|
|
broker_names = [subscription_dto.ResourceQualifier.NodeName]
|
|
|
|
nodes = {} # node-ptpstatus pairs
|
|
for broker in broker_names:
|
|
default_node_name = NodeInfoHelper.default_node_name(broker)
|
|
broker_pod_ip, supported_resource_types = self.__get_node_info(
|
|
default_node_name)
|
|
|
|
if not broker_pod_ip:
|
|
LOG.warning("Node {0} is not available yet".format(
|
|
default_node_name))
|
|
raise client_exception.NodeNotAvailable(broker)
|
|
|
|
if ResourceType.TypePTP not in supported_resource_types:
|
|
LOG.warning("Resource {0}@{1} is not available yet".format(
|
|
ResourceType.TypePTP, default_node_name))
|
|
raise client_exception.ResourceNotAvailable(
|
|
broker, ResourceType.TypePTP)
|
|
|
|
# get initial resource status
|
|
ptpstatus = self._query(default_node_name, broker_pod_ip,
|
|
resource_address, optional=None)
|
|
LOG.info("Initial ptpstatus for {0}:{1}".format(default_node_name,
|
|
ptpstatus))
|
|
|
|
# construct subscription entry
|
|
timestamp = None
|
|
if constants.PTP_V1_KEY in ptpstatus:
|
|
timestamp = ptpstatus[constants.PTP_V1_KEY].get(
|
|
'EventTimestamp', None)
|
|
ptpstatus = ptpstatus[constants.PTP_V1_KEY]
|
|
elif isinstance(ptpstatus, list):
|
|
LOG.debug("Format timestamps for standard subscription response")
|
|
for item in ptpstatus:
|
|
timestamp = item.get('time', None)
|
|
item['time'] = datetime.fromtimestamp(
|
|
item['time']).strftime(
|
|
'%Y-%m-%dT%H:%M:%S%fZ')
|
|
elif isinstance(ptpstatus, dict):
|
|
LOG.debug("Format timestamps for response with instance tags")
|
|
try:
|
|
for item in ptpstatus:
|
|
timestamp = ptpstatus[item].get('time', None)
|
|
# Change time from float to ascii format
|
|
ptpstatus[item]['time'] = datetime.fromtimestamp(
|
|
ptpstatus[item]['time']).strftime(
|
|
'%Y-%m-%dT%H:%M:%S%fZ')
|
|
except (TypeError, AttributeError):
|
|
LOG.debug("Format timestamp for single notification")
|
|
timestamp = ptpstatus.get('time', None)
|
|
ptpstatus['time'] = datetime.fromtimestamp(
|
|
ptpstatus['time']).strftime(
|
|
'%Y-%m-%dT%H:%M:%S%fZ')
|
|
nodes[default_node_name] = ptpstatus
|
|
|
|
subscription_orm = SubscriptionOrm(**subscription_dto.to_orm())
|
|
subscription_orm.InitialDeliveryTimestamp = timestamp
|
|
LOG.debug("Setting initial delivery timestamp %s", timestamp)
|
|
entry = self.subscription_repo.add(subscription_orm)
|
|
|
|
# Delivery the initial notification of ptp status
|
|
if version == 1:
|
|
subscription_dto2 = SubscriptionInfoV1(entry)
|
|
else:
|
|
subscription_dto2 = SubscriptionInfoV2(entry)
|
|
|
|
for node in nodes.items():
|
|
try:
|
|
subscription_helper.notify(subscription_dto2, node[1])
|
|
LOG.info("Initial ptpstatus of {0} is delivered successfully"
|
|
"".format(node[0]))
|
|
except requests.exceptions.RequestException as ex:
|
|
LOG.warning("initial ptpstatus is not delivered: {0}".format(
|
|
str(ex)))
|
|
raise client_exception.InvalidEndpoint(
|
|
subscription_dto2.EndpointUri)
|
|
except Exception as ex:
|
|
LOG.warning("Initial ptpstatus of {0} is not delivered:{1}"
|
|
"".format(node[0], str(ex)))
|
|
raise ex
|
|
|
|
try:
|
|
# commit the subscription entry
|
|
self.subscription_repo.commit()
|
|
self.daemon_control.refresh()
|
|
except Exception as ex:
|
|
LOG.warning("subscription is not added successfully:"
|
|
"{0}".format(str(ex)))
|
|
raise ex
|
|
|
|
return subscription_dto2
|
|
|
|
def remove_subscription(self, subscriptionid):
|
|
try:
|
|
# 1, delete entry
|
|
self.subscription_repo.delete_one(SubscriptionId=subscriptionid)
|
|
self.subscription_repo.commit()
|
|
# 2, refresh daemon
|
|
self.daemon_control.refresh()
|
|
except Exception as ex:
|
|
LOG.warning("subscription {0} is not deleted due to:"
|
|
"{1}/{2}".format(self.subscriptionid, type(ex),
|
|
str(ex)))
|
|
raise ex |