<commit_before>"""
Utils for the NAPALM modules and proxy.

.. seealso::

    - :mod:`NAPALM grains: select network devices based on their characteristics <salt.grains.napalm>`
    - :mod:`NET module: network basic features <salt.modules.napalm_network>`
    - :mod:`NTP operational and configuration management module <salt.modules.napalm_ntp>`
    - :mod:`BGP operational and configuration management module <salt.modules.napalm_bgp>`
    - :mod:`Routes details <salt.modules.napalm_route>`
    - :mod:`SNMP configuration module <salt.modules.napalm_snmp>`
    - :mod:`Users configuration management <salt.modules.napalm_users>`

.. versionadded:: 2017.7.0
"""


import copy
import importlib
import logging
import traceback
from functools import wraps

import salt.output
import salt.utils.args
import salt.utils.platform

try:
    # will try to import NAPALM
    # https://github.com/napalm-automation/napalm
    # pylint: disable=unused-import,no-name-in-module
    import napalm
    from napalm.base.exceptions import ConnectionClosedException

    # pylint: enable=unused-import,no-name-in-module
    HAS_NAPALM = True
    try:
        NAPALM_MAJOR = int(napalm.__version__.split(".")[0])
    except AttributeError:
        NAPALM_MAJOR = 0
except ImportError:
    HAS_NAPALM = False


log = logging.getLogger(__file__)


def is_proxy(opts):
    """
    Is this a NAPALM proxy?
    """
    return (
        salt.utils.platform.is_proxy()
        and opts.get("proxy", {}).get("proxytype") == "napalm"
    )


def is_always_alive(opts):
    """
    Is always alive required?
    """
    return opts.get("proxy", {}).get("always_alive", True)


def not_always_alive(opts):
    """
    Should this proxy be always alive?
    """
    return (is_proxy(opts) and not is_always_alive(opts)) or is_minion(opts)


def is_minion(opts):
    """
    Is this a NAPALM straight minion?
    """
    return not salt.utils.platform.is_proxy() and "napalm" in opts


def virtual(opts, virtualname, filename):
    """
    Returns the __virtual__.
    """
    if (HAS_NAPALM and NAPALM_MAJOR >= 2) and (is_proxy(opts) or is_minion(opts)):
        return virtualname
    else:
        return (
            False,
            '"{vname}"" {filename} cannot be loaded: '
            "NAPALM is not installed: ``pip install napalm``".format(
                vname=virtualname, filename="({filename})".format(filename=filename)
            ),
        )


def call(napalm_device, method, *args, **kwargs):
    """
    Calls arbitrary methods from the network driver instance.
    Please check the readthedocs_ page for the updated list of getters.

    .. _readthedocs: http://napalm.readthedocs.org/en/latest/support/index.html#getters-support-matrix

    method
        Specifies the name of the method to be called.

    *args
        Arguments.

    **kwargs
        More arguments.

    :return: A dictionary with three keys:

        * result (True/False): if the operation succeeded
        * out (object): returns the object as-is from the call
        * comment (string): provides more details in case the call failed
        * traceback (string): complete traceback in case of exception. \
        Please submit an issue including this traceback \
        on the `correct driver repo`_ and make sure to read the FAQ_

    .. _`correct driver repo`: https://github.com/napalm-automation/napalm/issues/new
    .. FAQ_: https://github.com/napalm-automation/napalm#faq

    Example:

    .. code-block:: python

        salt.utils.napalm.call(
            napalm_object,
            'cli',
            [
                'show version',
                'show chassis fan'
            ]
        )
    """
    result = False
    out = None
    opts = napalm_device.get("__opts__", {})
    retry = kwargs.pop("__retry", True)  # retry executing the task?
    force_reconnect = kwargs.get("force_reconnect", False)
    if force_reconnect:
        log.debug("Forced reconnection initiated")
        log.debug("The current opts (under the proxy key):")
        log.debug(opts["proxy"])
        opts["proxy"].update(**kwargs)
        log.debug("Updated to:")
        log.debug(opts["proxy"])
        napalm_device = get_device(opts)
    try:
        if not napalm_device.get("UP", False):
            raise Exception("not connected")
        # if connected will try to execute desired command
        kwargs_copy = {}
        kwargs_copy.update(kwargs)
        for karg, warg in kwargs_copy.items():
            # lets clear None arguments
            # to not be sent to NAPALM methods
            if warg is None:
                kwargs.pop(karg)
        out = getattr(napalm_device.get("DRIVER"), method)(*args, **kwargs)
        # calls the method with the specified parameters
        result = True
    except Exception as error:  # pylint: disable=broad-except
        # either not connected
        # either unable to execute the command
        hostname = napalm_device.get("HOSTNAME", "[unspecified hostname]")
        err_tb = (
            traceback.format_exc()
        )  # let's get the full traceback and display for debugging reasons.
        if isinstance(error, NotImplementedError):
            comment = (
                "{method} is not implemented for the NAPALM {driver} driver!".format(
                    method=method, driver=napalm_device.get("DRIVER_NAME")
                )
            )
        elif retry and isinstance(error, ConnectionClosedException):
            # Received disconection whilst executing the operation.<commit_msg>ConnectionClosedException isn't available in older version of the napalm Python library, so add code back that was handling that.<commit_after>"""
Utils for the NAPALM modules and proxy.

.. seealso::

    - :mod:`NAPALM grains: select network devices based on their characteristics <salt.grains.napalm>`
    - :mod:`NET module: network basic features <salt.modules.napalm_network>`
    - :mod:`NTP operational and configuration management module <salt.modules.napalm_ntp>`
    - :mod:`BGP operational and configuration management module <salt.modules.napalm_bgp>`
    - :mod:`Routes details <salt.modules.napalm_route>`
    - :mod:`SNMP configuration module <salt.modules.napalm_snmp>`
    - :mod:`Users configuration management <salt.modules.napalm_users>`

.. versionadded:: 2017.7.0
"""


import copy
import importlib
import logging
import traceback
from functools import wraps

import salt.output
import salt.utils.args
import salt.utils.platform

try:
    # will try to import NAPALM
    # https://github.com/napalm-automation/napalm
    # pylint: disable=unused-import,no-name-in-module
    import napalm

    # pylint: enable=unused-import,no-name-in-module
    HAS_NAPALM = True
    try:
        NAPALM_MAJOR = int(napalm.__version__.split(".")[0])
    except AttributeError:
        NAPALM_MAJOR = 0
except ImportError:
    HAS_NAPALM = False

try:
    # try importing ConnectionClosedException
    # from napalm-base
    # this exception has been introduced only in version 0.24.0
    from napalm.base.exceptions import ConnectionClosedException

    HAS_CONN_CLOSED_EXC_CLASS = True
except ImportError:
    HAS_CONN_CLOSED_EXC_CLASS = False

log = logging.getLogger(__file__)


def is_proxy(opts):
    """
    Is this a NAPALM proxy?
    """
    return (
        salt.utils.platform.is_proxy()
        and opts.get("proxy", {}).get("proxytype") == "napalm"
    )


def is_always_alive(opts):
    """
    Is always alive required?
    """
    return opts.get("proxy", {}).get("always_alive", True)


def not_always_alive(opts):
    """
    Should this proxy be always alive?
    """
    return (is_proxy(opts) and not is_always_alive(opts)) or is_minion(opts)


def is_minion(opts):
    """
    Is this a NAPALM straight minion?
    """
    return not salt.utils.platform.is_proxy() and "napalm" in opts


def virtual(opts, virtualname, filename):
    """
    Returns the __virtual__.
    """
    if (HAS_NAPALM and NAPALM_MAJOR >= 2) and (is_proxy(opts) or is_minion(opts)):
        return virtualname
    else:
        return (
            False,
            '"{vname}"" {filename} cannot be loaded: '
            "NAPALM is not installed: ``pip install napalm``".format(
                vname=virtualname, filename="({filename})".format(filename=filename)
            ),
        )


def call(napalm_device, method, *args, **kwargs):
    """
    Calls arbitrary methods from the network driver instance.
    Please check the readthedocs_ page for the updated list of getters.

    .. _readthedocs: http://napalm.readthedocs.org/en/latest/support/index.html#getters-support-matrix

    method
        Specifies the name of the method to be called.

    *args
        Arguments.

    **kwargs
        More arguments.

    :return: A dictionary with three keys:

        * result (True/False): if the operation succeeded
        * out (object): returns the object as-is from the call
        * comment (string): provides more details in case the call failed
        * traceback (string): complete traceback in case of exception. \
        Please submit an issue including this traceback \
        on the `correct driver repo`_ and make sure to read the FAQ_

    .. _`correct driver repo`: https://github.com/napalm-automation/napalm/issues/new
    .. FAQ_: https://github.com/napalm-automation/napalm#faq

    Example:

    .. code-block:: python

        salt.utils.napalm.call(
            napalm_object,
            'cli',
            [
                'show version',
                'show chassis fan'
            ]
        )
    """
    result = False
    out = None
    opts = napalm_device.get("__opts__", {})
    retry = kwargs.pop("__retry", True)  # retry executing the task?
    force_reconnect = kwargs.get("force_reconnect", False)
    if force_reconnect:
        log.debug("Forced reconnection initiated")
        log.debug("The current opts (under the proxy key):")
        log.debug(opts["proxy"])
        opts["proxy"].update(**kwargs)
        log.debug("Updated to:")
        log.debug(opts["proxy"])
        napalm_device = get_device(opts)
    try:
        if not napalm_device.get("UP", False):
            raise Exception("not connected")
        # if connected will try to execute desired command
        kwargs_copy = {}
        kwargs_copy.update(kwargs)
        for karg, warg in kwargs_copy.items():
            # lets clear None arguments
            # to not be sent to NAPALM methods
            if warg is None:
                kwargs.pop(karg)
        out = getattr(napalm_device.get("DRIVER"), method)(*args, **kwargs)
        # calls the method with the specified parameters
        result = True
    except Exception as error:  # pylint: disable=broad-except
        # either not connected
        # either unable to execute the command
        hostname = napalm_device.get("HOSTNAME", "[unspecified hostname]")
        err_tb = (
            traceback.format_exc()
        )  # let's get the full traceback and display for debugging reasons.
        if isinstance(error, NotImplementedError):
            comment = (
                "{method} is not implemented for the NAPALM {driver} driver!".format(
                    method=method, driver=napalm_device.get("DRIVER_NAME")
                )
            )
        elif (
            retry
            and HAS_CONN_CLOSED_EXC_CLASS
            and isinstance(error, ConnectionClosedException)
        ):
            # Received disconection whilst executing the operation.
{'fraction_non_alphanumeric': 0.10272664119485933, 'fraction_numerical': 0.0019103855505383813, 'mean_word_length': 2.541512915129151, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 19, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '194368', 'n_tokens_mistral': 3536, 'n_tokens_neox': 3284, 'n_words': 942}