
When adding a new subscription check for an existing matching one, considering the source uri hierachy. Deny a new individual if there is already a sync all subscription, and deny a new sync all if there is already an invidual one. After a new sync all subscription is created a set of event messages are sent to the client containing the initial state of each source down in the hierarchy. And, every time one of the source states changes a new message is sent. Test Plan: PASS: Build the container images PASS: Mannually deploy them and test with v2 client PASS: Create a '/././sync' subscription and check the event messages PASS: Check current subscription list PASS: Change GNSS sync state and check the event messages PASS: Attempt to create a new individual subscription and check it fails PASS: Delete the '/././sync' subscription PASS: Check current subscription list again Closes-bug: 2009188 Signed-off-by: Andre Mauricio Zelak <andre.zelak@windriver.com> Change-Id: I90b642e73f30fb1798f4a93ab5313411c177949c
137 lines
5.7 KiB
Python
137 lines
5.7 KiB
Python
#
|
|
# Copyright (c) 2021-2023 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
|
|
import json
|
|
import re
|
|
|
|
import requests
|
|
import logging
|
|
from datetime import datetime
|
|
from notificationclientsdk.common.helpers import constants
|
|
from notificationclientsdk.common.helpers import log_helper
|
|
from notificationclientsdk.exception import client_exception
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
log_helper.config_logger(LOG)
|
|
|
|
|
|
def notify(subscriptioninfo, notification, timeout=2, retry=3):
|
|
result = False
|
|
while True:
|
|
retry = retry - 1
|
|
try:
|
|
headers = {'Content-Type': 'application/json'}
|
|
url = subscriptioninfo.EndpointUri
|
|
for item in notification:
|
|
data = format_notification_data(subscriptioninfo, {item: notification[item]})
|
|
data = json.dumps(data)
|
|
response = requests.post(url, data=data, headers=headers,
|
|
timeout=timeout)
|
|
response.raise_for_status()
|
|
result = True
|
|
return response
|
|
except client_exception.InvalidResource as ex:
|
|
raise ex
|
|
except requests.exceptions.ConnectionError as errc:
|
|
if retry > 0:
|
|
LOG.warning("Retry notifying due to: {0}".format(str(errc)))
|
|
continue
|
|
raise errc
|
|
except requests.exceptions.Timeout as errt:
|
|
if retry > 0:
|
|
LOG.warning("Retry notifying due to: {0}".format(str(errt)))
|
|
continue
|
|
raise errt
|
|
except requests.exceptions.RequestException as ex:
|
|
LOG.warning("Failed to notify due to: {0}".format(str(ex)))
|
|
raise ex
|
|
except requests.exceptions.HTTPError as ex:
|
|
LOG.warning("Failed to notify due to: {0}".format(str(ex)))
|
|
raise ex
|
|
except Exception as ex:
|
|
LOG.warning("Failed to notify due to: {0}".format(str(ex)))
|
|
raise ex
|
|
|
|
return result
|
|
|
|
|
|
def format_notification_data(subscriptioninfo, notification):
|
|
if hasattr(subscriptioninfo, 'ResourceType'):
|
|
LOG.debug("format_notification_data: Found v1 subscription, "
|
|
"no formatting required.")
|
|
return notification
|
|
elif hasattr(subscriptioninfo, 'ResourceAddress'):
|
|
_, _, resource_path, _, _ = parse_resource_address(
|
|
subscriptioninfo.ResourceAddress)
|
|
if resource_path not in constants.RESOURCE_ADDRESS_MAPPINGS.keys():
|
|
raise client_exception.InvalidResource(resource_path)
|
|
resource_mapped_value = constants.RESOURCE_ADDRESS_MAPPINGS[
|
|
resource_path]
|
|
formatted_notification = {resource_mapped_value: []}
|
|
for instance in notification:
|
|
# Add the instance identifier to ResourceAddress for PTP lock-state
|
|
# and PTP clockClass
|
|
if notification[instance]['source'] in [
|
|
constants.SOURCE_SYNC_PTP_CLOCK_CLASS,
|
|
constants.SOURCE_SYNC_PTP_LOCK_STATE]:
|
|
temp_values = notification[instance].get('data', {}).get(
|
|
'values', [])
|
|
resource_address = temp_values[0].get('ResourceAddress', None)
|
|
if instance not in resource_address:
|
|
add_instance_name = resource_address.split('/', 3)
|
|
add_instance_name.insert(3, instance)
|
|
resource_address = '/'.join(add_instance_name)
|
|
notification[instance]['data']['values'][0][
|
|
'ResourceAddress'] = resource_address
|
|
formatted_notification[resource_mapped_value].append(
|
|
notification[instance])
|
|
for instance in formatted_notification[resource_mapped_value]:
|
|
this_delivery_time = instance['time']
|
|
if type(this_delivery_time) != str:
|
|
format_time = datetime.fromtimestamp(
|
|
float(this_delivery_time)).strftime('%Y-%m-%dT%H:%M:%S%fZ')
|
|
instance['time'] = format_time
|
|
else:
|
|
raise Exception("format_notification_data: No valid source "
|
|
"address found in notification")
|
|
LOG.debug("format_notification_data: Added parent key for client "
|
|
"consumption: %s" % formatted_notification)
|
|
return formatted_notification
|
|
|
|
|
|
def parse_resource_address(resource_address):
|
|
# The format of resource address is:
|
|
# /{clusterName}/{siteName}(/optional/hierarchy/..)/{nodeName}/{resource}
|
|
clusterName = resource_address.split('/')[1]
|
|
nodeName = resource_address.split('/')[2]
|
|
resource_path = '/' + re.split('[/]', resource_address, 3)[3]
|
|
resource_list = re.findall(r'[^/]+', resource_path)
|
|
if len(resource_list) == 4:
|
|
remove_optional = '/' + resource_list[0]
|
|
resource_path = resource_path.replace(remove_optional, '')
|
|
resource_address = resource_address.replace(remove_optional, '')
|
|
optional = resource_list[0]
|
|
LOG.debug("Optional hierarchy found when parsing resource address: %s"
|
|
% optional)
|
|
else:
|
|
optional = None
|
|
|
|
# resource_address is the full address without any optional hierarchy
|
|
# resource_path is the specific identifier for the resource
|
|
return clusterName, nodeName, resource_path, optional, resource_address
|
|
|
|
|
|
def set_nodename_in_resource_address(resource_address, nodename):
|
|
# The format of resource address is:
|
|
# /{clusterName}/{siteName}(/optional/hierarchy/..)/{nodeName}/{resource}
|
|
cluster, _, path, optional, _ = parse_resource_address(
|
|
resource_address)
|
|
resource_address = '/' + cluster + '/' + nodename
|
|
if optional:
|
|
resource_address += '/' + optional
|
|
resource_address += path
|
|
return resource_address
|