Skip to content

Commit

Permalink
add FFun FT filter, added network task plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
dmulyalin committed Mar 5, 2023
1 parent e306da5 commit d263c17
Show file tree
Hide file tree
Showing 9 changed files with 646 additions and 373 deletions.
1 change: 1 addition & 0 deletions docs/source/Tasks/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Task plugins define tasks to run against hosts. Reference `documentation <https:
netmiko_send_command_ps
netmiko_send_commands
netmiko_send_config
network
nr_test
puresnmp_call
pygnmi_call
Expand Down
1 change: 1 addition & 0 deletions docs/source/Tasks/network.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.. automodule:: nornir_salt.plugins.tasks.network
37 changes: 36 additions & 1 deletion nornir_salt/plugins/functions/FFun.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Filtering order::
FO -> FB -> FH -> FC -> FR -> FG -> FP -> FL -> FM -> FX -> FN
FO -> FB -> FH -> FC -> FR -> FG -> FP -> FL -> FM -> FX -> FT -> FN
.. note:: If multiple filters provided, hosts must pass all checks - ``AND`` logic - to succeed.
Expand Down Expand Up @@ -159,6 +159,21 @@
function called at the end to form a set of non matched hosts, that set used
with ``FL`` function to provide final match result.
FT - Filter Tags
------------------
Filter hosts by tags using host's data::
# will match all hosts with any of core or access tags
filtered_hosts = FFun(NornirObj, FL="core, access")
Sample host inventory data with tags definition::
hosts:
R1:
data:
tags: [core, access]
FFun sample usage
=================
Expand Down Expand Up @@ -298,6 +313,9 @@ def FFun(nr, check_if_has_filter=False, **kwargs):
if "FX" in kwargs:
ret = _filter_FX(ret, kwargs.pop("FX"))
has_filter = True
if "FT" in kwargs:
ret = _filter_FT(ret, kwargs.pop("FT"))
has_filter = True
if "FN" in kwargs:
ret = _filter_FN(ret, nr, kwargs.pop("FN"))
return (ret, has_filter) if check_if_has_filter else ret
Expand Down Expand Up @@ -492,3 +510,20 @@ def _filter_FX(ret, pattern):
)
else:
return ret.filter(filter_func=lambda h: not fnmatchcase(h.name, str(pattern)))


def _filter_FT(ret, tags_list):
"""
Function to filter hosts by tags
"""
tags_list = (
[i.strip() for i in tags_list.split(",")]
if isinstance(tags_list, str)
else tags_list
)
tags_set = set(tags_list)
return ret.filter(
filter_func=lambda h: True
if set(h.get("tags") or []).intersection(tags_set)
else False
)
2 changes: 2 additions & 0 deletions nornir_salt/plugins/tasks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .pyats_send_config import pyats_send_config
from .pyats_genie_api import pyats_genie_api
from .puresnmp_call import puresnmp_call
from .network import network

__all__ = (
"tcp_ping",
Expand Down Expand Up @@ -51,4 +52,5 @@
"pyats_send_config",
"pyats_genie_api",
"puresnmp_call",
"network",
)
2 changes: 1 addition & 1 deletion nornir_salt/plugins/tasks/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ def conn_open(
.. warning: Only ``netmiko`` ``conn_name`` connection plugin
supported for connections redispatching.
Given this host inventory:
Given this host inventory::
hosts:
ceos1:
Expand Down
147 changes: 147 additions & 0 deletions nornir_salt/plugins/tasks/network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"""
network
#######
Collection of task plugins to work with networks. Primarily
useful for testing DNS, host connectivity etc.
Sample usage
============
Code to invoke ``network`` task plugins::
from nornir import InitNornir
from nornir_salt.plugins.tasks import network
nr = InitNornir(config_file="config.yaml")
# resolve host's hostname
answers = nr.run(
task=network,
call="resolve_dns"
)
API reference
=============
.. autofunction:: nornir_salt.plugins.tasks.network.network
.. autofunction:: nornir_salt.plugins.tasks.network.resolve_dns
"""
import time
import traceback
import logging
import socket

try:
import dns.resolver

HAS_DNS = True
except ImportError:
HAS_DNS = False

from typing import Optional, Any, Dict, Union
from nornir.core.task import Result
from nornir_salt.utils.pydantic_models import model_network, model_network_resolve_dns
from nornir_salt.utils.yangdantic import ValidateFuncArgs

log = logging.getLogger(__name__)


@ValidateFuncArgs(model_network_resolve_dns)
def resolve_dns(
task,
servers: Union[list, str] = None,
use_host_name: bool = False,
timeout: float = 2.0,
ipv4: bool = True,
ipv6: bool = False,
) -> Result:
"""
Function to resolve host's hostname A and AAAA records.
``dnspython`` package need to be installed for this function to work::
pip install dnspython
:param server: list or comma separated string of IP addresses or FQDNs
of DNS servers to use
:param use_host_name: if True resolves host's name instead of host's hostname
:param timeout: number of seconds to wait for response from DNS server
:param ipv4: resolve 'A' record
:param ipv6: resolve 'AAAA' record
:return: returns a list of resolved addresses
"""
task.name = "resolve_dns"
if not HAS_DNS:
return Result(
host=task.host,
failed=True,
result="Failed importing dnspython, is it installed?",
)

res = set()
failed = False
exception_messages = []
resolver = dns.resolver.Resolver()
resolver.timeout = timeout

# decide on the records list to resolve
records = ["A"] if ipv4 else []
if ipv6:
records.append("AAAA")

# add custom DNS servers
if servers:
if isinstance(servers, str):
servers = [i.strip() for i in servers.split(",")]
resolver.nameservers = servers

# source FQDN to resolve
if use_host_name:
hostname = task.host.name
else:
hostname = task.host.hostname

# resolve DNS records
for record in records:
try:
answers = resolver.resolve(hostname, rdtype=record)
for answer in answers:
res.add(answer.address)
except:
tb = traceback.format_exc()
failed = True
exception_messages.append(
f"resolve_dns '{task.host.name}' failed to resolve '{record}' record for '{hostname}'\n{tb}"
)

return Result(
host=task.host,
failed=failed,
exception="\n".join(exception_messages) if exception_messages else None,
result=list(sorted(res)),
)


@ValidateFuncArgs(model_network)
def network(task, call, **kwargs) -> Result:
"""
Dispatcher function to call one of the functions.
:param call: (str) nickname of function to call
:param kwargs: (dict) function key-word arguments
:return: call function execution results
Call functions:
* resolve_dns - resolve hostname DNS
"""
dispatcher = {
"resolve_dns": resolve_dns,
# "reverse_dns": reverse_dns,
# "ping": ping,
# traceroute
# connect
}

return dispatcher[call](task, **kwargs)
25 changes: 25 additions & 0 deletions nornir_salt/utils/pydantic_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,3 +634,28 @@ class NornirInventory(BaseModel):
hosts: Optional[Dict[StrictStr, NornirInventoryHost]]
groups: Optional[Dict[StrictStr, NornirInventoryHost]]
defaults: Optional[NornirInventoryHost]


class model_network(BaseModel):
"""Model for Nornir network task plugin"""

task: Task
call: Optional[StrictStr]

class Config:
arbitrary_types_allowed = True
extra = "allow"


class model_network_resolve_dns(BaseModel):
"""Model for Nornir network resolve_dns task plugin"""

task: Task
servers: Optional[Union[List[StrictStr], StrictStr]]
use_host_name: Optional[StrictBool]
timeout: Optional[StrictFloat]
ipv4: Optional[StrictBool]
ipv6: Optional[StrictBool]

class Config:
arbitrary_types_allowed = True

0 comments on commit d263c17

Please sign in to comment.