Cole Walker 4178a86975 Integration work for ORAN notification
Several improvements and fixes to enable the end-to-end functionality of
all of the components in support of the O-RAN Spec Compliant Timing API
Notification work.

1. Add time stamps to logging for notificationservice and
notificationclient
2. Add support for the "optional" hierarchy in the resource address
which allows the client to query the status of a specific ptp instances.
ie. get the status of instance ptp1 rather than all ptp instances
3. Add a parent key to the returned notification data so that multiple
statuses can be returned to the client with a single notification'
4. Reworked the notificationservice daemonset to start its process
directly rather than using an intermediary script. This allows the
container logs to show properly via kubectl logs and will also allow the
container to crash properly if the program errors out.
5. Reworked the helm values for ptp4l and ts2phc instances to allow
users to supply overrides with multiple instances

Test plan:
PASS: PTP notification v1 compatibility
PASS: GET all v2 resources
PASS: SUBSCRIBE/LIST/DELETE v2 resources
PASS: Build and deploy containers/fluxcd app

Story: 2010056
Task: 46226

Change-Id: Id471fdc0815afdcc5639e81c6457616e268e6cd7
Signed-off-by: Cole Walker <cole.walker@windriver.com>
2022-09-14 11:02:43 -04:00

254 lines
11 KiB
Python

#
# Copyright (c) 2021-2022 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import json
import logging
from notificationclientsdk.model.dto.subscription import SubscriptionInfoV1
from notificationclientsdk.model.dto.subscription import SubscriptionInfoV2
from notificationclientsdk.model.dto.resourcetype import ResourceType
from notificationclientsdk.common.helpers.nodeinfo_helper import NodeInfoHelper
from notificationclientsdk.common.helpers import subscription_helper
from notificationclientsdk.model.dto.broker_state import BrokerState
LOG = logging.getLogger(__name__)
from notificationclientsdk.common.helpers import log_helper
log_helper.config_logger(LOG)
class BrokerStateManager:
'''
Manager to manage broker states
Note: Now it is not thread safe
'''
def __init__(self):
self.broker_state_map = {}
self.disabled_brokers = []
self.subscription_refresh_iteration = 0
def count_brokers(self):
return len(self.broker_state_map)
def add_broker(self, broker_name):
brokerstate = self.broker_state_map.get(broker_name, None)
if not brokerstate:
brokerstate = BrokerState(
BrokerName=broker_name,
ResourceTypes=[], ResourceTypesSubscribed={})
brokerstate.update_connection_state(False)
self.broker_state_map[broker_name] = brokerstate
return brokerstate
def disable_broker(self, broker_name):
if not broker_name in self.disabled_brokers:
self.disabled_brokers.append(broker_name)
def remove_broker(self, broker_name):
self.broker_state_map.pop(broker_name, None)
def refresh_by_nodeinfos(self, nodeinfos_orm):
broker_state_changed = False
for s in nodeinfos_orm or []:
broker_state_changed = self.refresh_by_nodeinfo(s) or broker_state_changed
return broker_state_changed
def refresh_by_nodeinfo(self, nodeinfo_orm):
brokerstate = self.broker_state_map.get(nodeinfo_orm.NodeName, None)
if not brokerstate:
return False
if nodeinfo_orm.Status == 1:
brokerstate.update_connection_state(True)
else:
brokerstate.update_connection_state(False)
brokerstate.update_broker_ip(nodeinfo_orm.PodIP)
brokerstate.update_resources(json.loads(nodeinfo_orm.ResourceTypes))
return brokerstate.is_broker_ip_changed() or brokerstate.is_resources_changed()
def refresh_by_subscriptions(self, subscriptions_orm):
broker_state_changed = False
# 1, refresh iteration
self.subscription_refresh_iteration = self.subscription_refresh_iteration + 1
# 2, refresh resource subscriptions by subscription record
for s in subscriptions_orm or []:
broker_state_changed = self.__refresh_by_subscription(s) or broker_state_changed
# 3, mark broker state change by checking if any obsolete resources
broker_state_changed = broker_state_changed or self.any_obsolete_broker()
return broker_state_changed
def any_obsolete_broker(self):
for broker_name, brokerstate in self.broker_state_map.items():
try:
if brokerstate.any_obsolete_subscription(
self.subscription_refresh_iteration):
return True
except Exception as ex:
LOG.warning(
"failed to check obsoleted resources@{0}:{1}".format(broker_name, str(ex)))
continue
return False
def __refresh_by_subscription(self, subscription_orm):
changed = False
broker_name = None
if getattr(subscription_orm, 'ResourceType') is not None:
subscription = SubscriptionInfoV1(subscription_orm)
resource = subscription.ResourceType
# assume PTP and not wildcard
if resource == ResourceType.TypePTP:
broker_name = subscription.ResourceQualifier.NodeName
else:
# ignore the subscription due to unsupported type
LOG.debug(
"Ignore the subscription for: {0}".format(subscription_orm.SubscriptionId))
return False
else:
subscription = SubscriptionInfoV2(subscription_orm)
_, nodename, resource, _, _ = subscription_helper.parse_resource_address(
subscription.ResourceAddress)
broker_name = nodename
LOG.debug(
"subscription:{0}, Status:{1}".format(subscription.to_dict(), subscription_orm.Status))
if subscription_orm.Status != 1:
return False
if not broker_name:
# ignore the subscription due to unsupported type
LOG.debug("Ignore the subscription for: {0}".format(subscription.SubscriptionId))
return False
enumerated_broker_names = NodeInfoHelper.enumerate_nodes(broker_name)
if not enumerated_broker_names:
LOG.debug("Failed to enumerate broker names for {0}".format(broker_name))
return False
for expanded_broker_name in enumerated_broker_names:
brokerstate = self.broker_state_map.get(expanded_broker_name, None)
if not brokerstate:
brokerstate = self.add_broker(expanded_broker_name)
changed = True
changed = changed or (brokerstate.is_resource_subscribed(resource) == False)
brokerstate.try_subscribe_resource(resource, self.subscription_refresh_iteration)
return changed
def syncup_broker_watchers(self, broker_connection_manager):
'''sync up brokers state to broker connection manager'''
aggregated_result = True
interested_brokers = []
removed_brokers = []
# 1, clean all obsolete resource subscriptions
# and disable broker in case no active resource subscription
for broker_name, brokerstate in self.broker_state_map.items():
try:
brokerstate.unsubscribe_resource_obsolete(self.subscription_refresh_iteration)
if not brokerstate.any_resource_subscribed():
LOG.debug("disable broker@{0} due to no subscription".format(broker_name))
self.disable_broker(broker_name)
except Exception as ex:
LOG.warning(
"failed to clean obsolete subscribed resources@{0}:{1}".format(
broker_name, str(ex)))
continue
# 2, stop watching all disabled brokers
for broker_name in self.disabled_brokers:
try:
LOG.debug("stop watching due to disabled: {0}".format(broker_name))
result = broker_connection_manager.stop_watching_broker(
broker_name)
self.remove_broker(broker_name)
removed_brokers.append(broker_name)
aggregated_result = aggregated_result and result
except Exception as ex:
LOG.warning(
"failed to clean disabled broker@{0}: {1}".format(
broker_name, str(ex)))
aggregated_result = False
continue
self.disabled_brokers.clear()
# 3, start/restart watching remains brokers
for broker_name, brokerstate in self.broker_state_map.items():
interested_brokers.append(broker_name)
try:
result = True
is_connected = brokerstate.is_connected()
is_watching = broker_connection_manager.is_watching_broker(
broker_name)
if not is_connected:
if is_watching:
LOG.debug("Stop watching due to disconnected: {0}".format(broker_name))
result = broker_connection_manager.stop_watching_broker(
broker_name)
elif is_connected:
# note: start/restart watcher will update resources as well
if not is_watching:
LOG.debug("Start watching due to connected: {0}".format(broker_name))
result = broker_connection_manager.start_watching_broker(
brokerstate)
elif brokerstate.is_broker_ip_changed():
LOG.debug("Restart watching due to IP changed: {0}".format(broker_name))
result = broker_connection_manager.restart_watching_broker(
brokerstate)
elif brokerstate.is_connection_state_changed():
# trigger to sync up notification after (re-)connection
LOG.debug("Trigger to re-sync up data: {0}".format(broker_name))
result = brokerstate.signal_data_syncup()
elif brokerstate.is_resource_subscribed_changed() or \
brokerstate.is_resources_changed():
LOG.debug(
"Update watching due to resources changed: {0}".format(broker_name))
result = broker_connection_manager.update_watching_resources(brokerstate)
# leave the signals as it is to re-sync up in next loop in case of failure
if result:
# assumption to avoid race condition: same thread to manipulate brokerstate
brokerstate.ack_connection_state_changed()
brokerstate.ack_broker_ip_changed()
brokerstate.ack_resource_subscribed_changed()
brokerstate.ack_resources_changed()
aggregated_result = aggregated_result and result
except Exception as ex:
LOG.warning("failed to sync up broker watcher:{0},{1}".format(broker_name, str(ex)))
aggregated_result = False
continue
return aggregated_result, interested_brokers, removed_brokers
def syncup_broker_data(self, broker_connection_manager):
'''sync up to get rid of stall data'''
aggregated_result = True
synced_brokers = []
unsynced_brokers = []
for broker_name, brokerstate in self.broker_state_map.items():
try:
if brokerstate.is_connected() and brokerstate.is_data_syncup():
LOG.debug("Try to sync up broker data:{0}".format(broker_name))
result = result and broker_connection_manager.syncup_broker_data(
brokerstate)
if result:
# assumption to avoid race condition: same thread to manipulate brokerstate
brokerstate.ack_data_syncup()
synced_brokers.append(broker_name)
else:
unsynced_brokers.append(broker_name)
aggregated_result = aggregated_result and result
except Exception as ex:
unsynced_brokers.append(broker_name)
LOG.warning("failed to sync up broker data:{0}".format(str(ex)))
aggregated_result = False
continue
return aggregated_result, synced_brokers, unsynced_brokers