Commit 444b5091 authored by Waleed Akbar's avatar Waleed Akbar
Browse files

feat(openconfig): Enhance NETCONF session management and update XML template...

feat(openconfig): Enhance NETCONF session management and update XML template for component configuration
parent 83a03497
Loading
Loading
Loading
Loading
+46 −5
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ logging.getLogger('ncclient.transport.ssh').setLevel(logging.DEBUG if DEBUG_MODE
logging.getLogger('apscheduler.executors.default').setLevel(logging.INFO if DEBUG_MODE else logging.ERROR)
logging.getLogger('apscheduler.scheduler').setLevel(logging.INFO if DEBUG_MODE else logging.ERROR)
logging.getLogger('monitoring-client').setLevel(logging.INFO if DEBUG_MODE else logging.ERROR)
LOGGER = logging.getLogger(__name__)

RE_GET_ENDPOINT_FROM_INTERFACE_KEY = re.compile(r'.*interface\[([^\]]+)\].*')
RE_GET_ENDPOINT_FROM_INTERFACE_XPATH = re.compile(r".*interface\[oci\:name\='([^\]]+)'\].*")
@@ -52,7 +53,7 @@ SAMPLE_RESOURCE_KEY = 'interfaces/interface/state/counters'

MAX_RETRIES = 15
DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0)
RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect')
RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='reconnect')

class NetconfSessionHandler:
    def __init__(self, address : str, port : int, **settings) -> None:
@@ -79,6 +80,8 @@ class NetconfSessionHandler:

    def connect(self):
        with self.__lock:
            # Replace any stale manager state before creating a fresh session
            self._safe_close()
            self.__manager = connect_ssh(
                host=self.__address, port=self.__port, username=self.__username, password=self.__password,
                device_params=self.__device_params, manager_params=self.__manager_params, nc_params=self.__nc_params,
@@ -87,10 +90,43 @@ class NetconfSessionHandler:
            self.__candidate_supported = ':candidate' in self.__manager.server_capabilities
            self.__connected.set()

    def _safe_close(self) -> None:
        """Close the current manager session without raising, then reset state."""
        if not self.__connected.is_set() and self.__manager is None:
            return
        try:
            if self.__manager is not None:
                self.__manager.close_session()
        except Exception:  # pylint: disable=broad-except
            LOGGER.warning(
                'Ignoring error during session close for %s:%s',
                self.__address, self.__port,
                exc_info=True,
            )
        finally:
            self.__manager = None
            self.__connected.clear()
            self.__candidate_supported = False

    def reconnect(self) -> None:
        """Close any existing session and establish a fresh NETCONF connection.

        Used by the retry decorator after ncclient reports a session loss, so
        the next retry does not reuse a stale manager.
        """
        LOGGER.info(
            'NETCONF session lost for %s:%s; reconnecting...',
            self.__address, self.__port,
        )
        self._safe_close()
        self.connect()

    def disconnect(self):
        if not self.__connected.is_set(): return
        # if not self.__connected.is_set(): return
        LOGGER.info('Disconnecting NETCONF session for %s:%s', 
                    self.__address, self.__port)
        with self.__lock:
            self.__manager.close_session()
            self._safe_close()

    @property
    def use_candidate(self): return self.__candidate_supported and not self.__force_running
@@ -122,9 +158,14 @@ class NetconfSessionHandler:
    ):
        if config == EMPTY_CONFIG: return
        with self.__lock:
            self.__manager.edit_config(
            response = self.__manager.edit_config(
                config, target=target, default_operation=default_operation, test_option=test_option,
                error_option=error_option, format=format)
            LOGGER.info(
                'NETCONF edit-config reply from %s:%s ok=%s response=%s',
                self.__address, self.__port, getattr(response, 'ok', None), str(response),
            )
            return response

    @RETRY_DECORATOR
    def locked(self, target):
@@ -262,7 +303,7 @@ def edit_config(
                #results[i] = e # if validation fails, store the exception
                results.append(e)

        if not commit_per_rule:
        if not commit_per_rule and target == 'candidate':
            try:
                netconf_handler.commit()
            except Exception as e: # pylint: disable=broad-except
+15 −2
Original line number Diff line number Diff line
@@ -13,10 +13,22 @@
# limitations under the License.

import logging, time
from ncclient.transport.errors import SessionCloseError, TransportError
from common.tools.client.RetryDecorator import delay_linear

LOGGER = logging.getLogger(__name__)


def _is_reconnectable_error(exc: Exception) -> bool:
    """Return True when *exc* indicates a lost NETCONF session that can be
    recovered by reconnecting the transport."""
    if isinstance(exc, (SessionCloseError, TransportError, EOFError)):
        return True
    if isinstance(exc, OSError) and str(exc) == 'Socket is closed':
        return True
    return False


def retry(max_retries=0, delay_function=delay_linear(initial=0, increment=0),
          prepare_method_name=None, prepare_method_args=[], prepare_method_kwargs={}):
    def _reconnect(func):
@@ -28,8 +40,9 @@ def retry(max_retries=0, delay_function=delay_linear(initial=0, increment=0),
            while not given_up:
                try:
                    return func(self, *args, **kwargs)
                except OSError as e:
                    if str(e) != 'Socket is closed': raise
                except Exception as e:
                    if not _is_reconnectable_error(e):
                        raise

                    num_try += 1
                    given_up = num_try > max_retries
+14 −4
Original line number Diff line number Diff line
<components xmlns="http://openconfig.net/yang/platform">
    {% if components is defined and components %}
    {% for component in components %}
    <component{% if operation is defined %} xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="{{operation}}"{% endif %}>
        <name>{{name}}</name>
        <name>{{component.name}}</name>
        <optical-channel xmlns="http://openconfig.net/yang/terminal-device-digital-subcarriers">
            {% if operation is defined and operation != 'delete' %}
            {% if operation is undefined or operation != 'delete' %}
            <config>
                {% if digital_sub_carriers_group is defined and digital_sub_carriers_group %}
                    {% for group in digital_sub_carriers_group %}
                {% if component.frequency is defined %}
                <frequency>{{component.frequency}}</frequency>
                {% endif %}
                {% if component.target_output_power is defined %}
                <target-output-power>{{component.target_output_power}}</target-output-power>
                {% endif %}
                {% if component.digital_sub_carriers_group is defined and component.digital_sub_carriers_group %}
                    {% for group in component.digital_sub_carriers_group %}
                    <digital-subcarriers-group>
                        <digital-subcarriers-group-id>{{group.digital_sub_carriers_group_id}}</digital-subcarriers-group-id>
                        {% if group.digital_sub_carrier_id is defined and group.digital_sub_carrier_id %}
@@ -23,4 +31,6 @@
            {% endif %}
        </optical-channel>
    </component>
    {% endfor %}
    {% endif %}
</components>