mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Proxy Minion for network devices using Napalm library
This commit is contained in:
parent
f833b6f5b6
commit
8ad5794b60
@ -198,6 +198,7 @@ Full list of builtin execution modules
|
|||||||
nacl
|
nacl
|
||||||
nagios
|
nagios
|
||||||
nagios_rpc
|
nagios_rpc
|
||||||
|
napalm_network
|
||||||
netaddress
|
netaddress
|
||||||
netbsd_sysctl
|
netbsd_sysctl
|
||||||
netbsdservice
|
netbsdservice
|
||||||
|
6
doc/ref/modules/all/salt.modules.napalm_network.rst
Normal file
6
doc/ref/modules/all/salt.modules.napalm_network.rst
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
salt.modules.napalm_network module
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: salt.modules.napalm_network
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
@ -15,6 +15,7 @@ Full list of builtin proxy modules
|
|||||||
fx2
|
fx2
|
||||||
junos
|
junos
|
||||||
marathon
|
marathon
|
||||||
|
napalm
|
||||||
phillips_hue
|
phillips_hue
|
||||||
rest_sample
|
rest_sample
|
||||||
ssh_sample
|
ssh_sample
|
||||||
|
6
doc/ref/proxy/all/salt.proxy.napalm.rst
Normal file
6
doc/ref/proxy/all/salt.proxy.napalm.rst
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
================
|
||||||
|
salt.proxy.napalm
|
||||||
|
================
|
||||||
|
|
||||||
|
.. automodule:: salt.proxy.napalm
|
||||||
|
:members:
|
316
salt/modules/napalm_network.py
Normal file
316
salt/modules/napalm_network.py
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Basic functions from Napalm library
|
||||||
|
'''
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# module properties
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__virtualname__ = 'net'
|
||||||
|
__proxyenabled__ = ['napalm']
|
||||||
|
# uses NAPALM-based proxy to interact with network devices
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# property functions
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def __virtual__():
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# helper functions -- will not be exported
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_list(input_list, search_key, search_value):
|
||||||
|
|
||||||
|
output_list = list()
|
||||||
|
|
||||||
|
for dictionary in input_list:
|
||||||
|
if dictionary.get(search_key) == search_value:
|
||||||
|
output_list.append(dictionary)
|
||||||
|
|
||||||
|
return output_list
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_dict(input_dict, search_key, search_value):
|
||||||
|
|
||||||
|
output_dict = dict()
|
||||||
|
|
||||||
|
for key, key_list in input_dict.iteritems():
|
||||||
|
key_list_filtered = _filter_list(key_list, search_key, search_value)
|
||||||
|
if key_list_filtered:
|
||||||
|
output_dict[key] = key_list_filtered
|
||||||
|
|
||||||
|
return output_dict
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# callable functions
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def ping():
|
||||||
|
'''
|
||||||
|
is the device alive ?
|
||||||
|
|
||||||
|
CLI example:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
salt myminion net.ping
|
||||||
|
'''
|
||||||
|
|
||||||
|
return {
|
||||||
|
'out': __proxy__['napalm.ping']()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def cli(*commands):
|
||||||
|
|
||||||
|
"""
|
||||||
|
NAPALM returns a dictionary with the output of all commands passed as arguments:
|
||||||
|
|
||||||
|
CLI example:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
salt myminion net.cli "show version" "show route 8.8.8.8"
|
||||||
|
|
||||||
|
:param commands: list of raw commands to execute on device
|
||||||
|
|
||||||
|
Example:
|
||||||
|
{
|
||||||
|
u'show version and haiku' : u'''Hostname: re0.edge01.arn01
|
||||||
|
Model: mx480
|
||||||
|
Junos: 13.3R6.5
|
||||||
|
Help me, Obi-Wan
|
||||||
|
I just saw Episode Two
|
||||||
|
You're my only hope
|
||||||
|
''',
|
||||||
|
u'show chassis fan' : u'''Item Status RPM Measurement
|
||||||
|
Top Rear Fan OK 3840 Spinning at intermediate-speed
|
||||||
|
Bottom Rear Fan OK 3840 Spinning at intermediate-speed
|
||||||
|
Top Middle Fan OK 3900 Spinning at intermediate-speed
|
||||||
|
Bottom Middle Fan OK 3840 Spinning at intermediate-speed
|
||||||
|
Top Front Fan OK 3810 Spinning at intermediate-speed
|
||||||
|
Bottom Front Fan OK 3840 Spinning at intermediate-speed
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
return __proxy__['napalm.call'](
|
||||||
|
'cli',
|
||||||
|
**{
|
||||||
|
'commands': list(commands)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# thus we can display the output as is
|
||||||
|
# in case of errors, they'll be catched in the proxy
|
||||||
|
|
||||||
|
|
||||||
|
def arp(interface='', ipaddr='', macaddr=''):
|
||||||
|
|
||||||
|
"""
|
||||||
|
NAPALM returns a list of dictionaries with details of the ARP entries:
|
||||||
|
[{INTERFACE, MAC, IP, AGE}]
|
||||||
|
|
||||||
|
CLI example:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
salt myminion net.arp
|
||||||
|
salt myminion net.arp macaddr='5c:5e:ab:da:3c:f0'
|
||||||
|
|
||||||
|
:param interface: interface name to filter on
|
||||||
|
:param ipaddr: IP address to filter on
|
||||||
|
:param macaddr: MAC address to filter on
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'interface' : 'MgmtEth0/RSP0/CPU0/0',
|
||||||
|
'mac' : '5c:5e:ab:da:3c:f0',
|
||||||
|
'ip' : '172.17.17.1',
|
||||||
|
'age' : 1454496274.84
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'interface': 'MgmtEth0/RSP0/CPU0/0',
|
||||||
|
'mac' : '66:0e:94:96:e0:ff',
|
||||||
|
'ip' : '172.17.17.2',
|
||||||
|
'age' : 1435641582.49
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
|
proxy_output = __proxy__['napalm.call'](
|
||||||
|
'get_arp_table',
|
||||||
|
**{
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if not proxy_output.get('result'):
|
||||||
|
return proxy_output
|
||||||
|
|
||||||
|
arp_table = proxy_output.get('out')
|
||||||
|
|
||||||
|
if interface:
|
||||||
|
arp_table = _filter_list(arp_table, 'interface', interface)
|
||||||
|
|
||||||
|
if ipaddr:
|
||||||
|
arp_table = _filter_list(arp_table, 'ip', ipaddr)
|
||||||
|
|
||||||
|
if macaddr:
|
||||||
|
arp_table = _filter_list(arp_table, 'mac', macaddr)
|
||||||
|
|
||||||
|
proxy_output.update({
|
||||||
|
'out': arp_table
|
||||||
|
})
|
||||||
|
|
||||||
|
return proxy_output
|
||||||
|
|
||||||
|
|
||||||
|
def ipaddrs():
|
||||||
|
'''
|
||||||
|
Returns IP addresses on the device
|
||||||
|
|
||||||
|
CLI example:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
salt myminion net.ipaddrs
|
||||||
|
'''
|
||||||
|
return __proxy__['napalm.call'](
|
||||||
|
'get_interfaces_ip',
|
||||||
|
**{
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def lldp(interface=''):
|
||||||
|
|
||||||
|
"""
|
||||||
|
returns LLDP neighbors
|
||||||
|
|
||||||
|
CLI example:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
salt myminion net.lldp
|
||||||
|
salt myminion net.lldp interface='TenGigE0/0/0/8'
|
||||||
|
|
||||||
|
:param interface: interface name to filter on
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
{
|
||||||
|
'TenGigE0/0/0/8': [
|
||||||
|
{
|
||||||
|
'parent_interface': u'Bundle-Ether8',
|
||||||
|
'interface_description': u'TenGigE0/0/0/8',
|
||||||
|
'remote_chassis_id': u'8c60.4f69.e96c',
|
||||||
|
'remote_system_name': u'switch',
|
||||||
|
'remote_port': u'Eth2/2/1',
|
||||||
|
'remote_port_description': u'Ethernet2/2/1',
|
||||||
|
'remote_system_description': u'''Cisco Nexus Operating System (NX-OS) Software 7.1(0)N1(1a)
|
||||||
|
TAC support: http://www.cisco.com/tac
|
||||||
|
Copyright (c) 2002-2015, Cisco Systems, Inc. All rights reserved.''',
|
||||||
|
'remote_system_capab': u'B, R',
|
||||||
|
'remote_system_enable_capab': u'B'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
proxy_output = __proxy__['napalm.call'](
|
||||||
|
'get_lldp_neighbors_detail',
|
||||||
|
**{
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if not proxy_output.get('result'):
|
||||||
|
return proxy_output
|
||||||
|
|
||||||
|
lldp_neighbors = proxy_output.get('out')
|
||||||
|
|
||||||
|
if interface:
|
||||||
|
lldp_neighbors = {interface: lldp_neighbors.get(interface)}
|
||||||
|
|
||||||
|
proxy_output.update({
|
||||||
|
'out': lldp_neighbors
|
||||||
|
})
|
||||||
|
|
||||||
|
return proxy_output
|
||||||
|
|
||||||
|
|
||||||
|
def mac(address='', interface='', vlan=0):
|
||||||
|
|
||||||
|
"""
|
||||||
|
returns device MAC address table
|
||||||
|
|
||||||
|
CLI example:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
salt myminion net.mac
|
||||||
|
salt myminion net.mac vlan=10
|
||||||
|
|
||||||
|
:param address: MAC address to filter on
|
||||||
|
:param interface: interface name to filter on
|
||||||
|
:param vlan: vlan identifier
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'mac' : '00:1c:58:29:4a:71',
|
||||||
|
'interface' : 'xe-3/0/2',
|
||||||
|
'static' : False,
|
||||||
|
'active' : True,
|
||||||
|
'moves' : 1,
|
||||||
|
'vlan' : 10,
|
||||||
|
'last_move' : 1454417742.58
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'mac' : '8c:60:4f:58:e1:c1',
|
||||||
|
'interface' : 'xe-1/0/1',
|
||||||
|
'static' : False,
|
||||||
|
'active' : True,
|
||||||
|
'moves' : 2,
|
||||||
|
'vlan' : 42,
|
||||||
|
'last_move' : 1453191948.11
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
proxy_output = __proxy__['napalm.call'](
|
||||||
|
'get_mac_address_table',
|
||||||
|
**{
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if not proxy_output.get('result'):
|
||||||
|
# if negative, leave the output unchanged
|
||||||
|
return proxy_output
|
||||||
|
|
||||||
|
mac_address_table = proxy_output.get('out')
|
||||||
|
|
||||||
|
if vlan and isinstance(int, vlan):
|
||||||
|
mac_address_table = {vlan: mac_address_table.get(vlan)}
|
||||||
|
|
||||||
|
if address:
|
||||||
|
mac_address_table = _filter_dict(mac_address_table, 'mac', address)
|
||||||
|
|
||||||
|
if interface:
|
||||||
|
mac_address_table = _filter_dict(mac_address_table, 'interface', interface)
|
||||||
|
|
||||||
|
proxy_output.update({
|
||||||
|
'out': mac_address_table
|
||||||
|
})
|
||||||
|
|
||||||
|
return proxy_output
|
171
salt/proxy/napalm.py
Normal file
171
salt/proxy/napalm.py
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
THis module allows Salt interact with network devices via NAPALM library
|
||||||
|
(https://github.com/napalm-automation/napalm)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__file__)
|
||||||
|
|
||||||
|
from napalm import get_network_driver
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# proxy properties
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__proxyenabled__ = ['napalm']
|
||||||
|
# proxy name
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# global variables
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
NETWORK_DEVICE = {}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# property functions
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def __virtual__():
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# helper functions -- will not be exported
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# Salt specific proxy functions
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def init(opts):
|
||||||
|
'''
|
||||||
|
Perform any needed setup.
|
||||||
|
'''
|
||||||
|
NETWORK_DEVICE['HOSTNAME'] = opts.get('proxy', {}).get('host')
|
||||||
|
NETWORK_DEVICE['USERNAME'] = opts.get('proxy', {}).get('username')
|
||||||
|
NETWORK_DEVICE['PASSWORD'] = opts.get('proxy', {}).get('passwd')
|
||||||
|
NETWORK_DEVICE['DRIVER_NAME'] = opts.get('proxy', {}).get('driver')
|
||||||
|
|
||||||
|
NETWORK_DEVICE['UP'] = False
|
||||||
|
|
||||||
|
_driver_ = get_network_driver(NETWORK_DEVICE.get('DRIVER_NAME'))
|
||||||
|
# get driver object form NAPALM
|
||||||
|
|
||||||
|
optional_args = {
|
||||||
|
'config_lock': False # to avoid locking config DB
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
NETWORK_DEVICE['DRIVER'] = _driver_(
|
||||||
|
NETWORK_DEVICE.get('HOSTNAME', ''),
|
||||||
|
NETWORK_DEVICE.get('USERNAME', ''),
|
||||||
|
NETWORK_DEVICE.get('PASSWORD', ''),
|
||||||
|
optional_args=optional_args
|
||||||
|
)
|
||||||
|
NETWORK_DEVICE.get('DRIVER').open()
|
||||||
|
# no exception raised here, means connection established
|
||||||
|
NETWORK_DEVICE['UP'] = True
|
||||||
|
except Exception as error:
|
||||||
|
log.error(
|
||||||
|
"Cannot connect to {hostname} as {username}. Please check error: {error}".format(
|
||||||
|
hostname=NETWORK_DEVICE.get('HOSTNAME', ''),
|
||||||
|
username=NETWORK_DEVICE.get('USERNAME', ''),
|
||||||
|
error=error
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def ping():
|
||||||
|
'''
|
||||||
|
is the device responding ?
|
||||||
|
'''
|
||||||
|
return NETWORK_DEVICE['UP']
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown(opts):
|
||||||
|
'''
|
||||||
|
use napalm close()
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
if not NETWORK_DEVICE.get('UP', False):
|
||||||
|
raise Exception('not connected!')
|
||||||
|
NETWORK_DEVICE.get('DRIVER').close()
|
||||||
|
except Exception as error:
|
||||||
|
log.error(
|
||||||
|
'Cannot close connection with {hostname}! Please check error: {error}'.format(
|
||||||
|
hostname=NETWORK_DEVICE.get('HOSTNAME', '[unknown hostname]'),
|
||||||
|
error=error
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# Callable functions
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def call(method, **params):
|
||||||
|
|
||||||
|
"""
|
||||||
|
This function calls methods from the NAPALM driver object.
|
||||||
|
Available methods:
|
||||||
|
|
||||||
|
============================== ===== ===== ====== ======= ====== ====== ===== =========
|
||||||
|
_ EOS JunOS IOS-XR FortiOS IBM NXOS IOS Pluribus
|
||||||
|
============================== ===== ===== ====== ======= ====== ====== ===== =========
|
||||||
|
**cli** |yes| |yes| |yes| |no| |no| |yes| |yes| |yes|
|
||||||
|
**get_facts** |yes| |yes| |yes| |yes| |no| |yes| |yes| |yes|
|
||||||
|
**get_interfaces** |yes| |yes| |yes| |yes| |no| |yes| |yes| |yes|
|
||||||
|
**get_lldp_neighbors** |yes| |yes| |yes| |yes| |no| |no| |yes| |yes|
|
||||||
|
**get_lldp_neighbors_detail** |yes| |yes| |yes| |no| |no| |yes| |no| |yes|
|
||||||
|
**get_bgp_neighbors** |yes| |yes| |yes| |yes| |no| |no| |yes| |no|
|
||||||
|
**get_bgp_neighbors_detail** |yes| |yes| |no| |no| |no| |no| |no| |no|
|
||||||
|
**get_bgp_config** |yes| |yes| |yes| |no| |no| |no| |no| |no|
|
||||||
|
**get_environment** |yes| |yes| |yes| |yes| |no| |no| |yes| |no|
|
||||||
|
**get_mac_address_table** |yes| |yes| |yes| |no| |no| |yes| |no| |yes|
|
||||||
|
**get_arp_table** |yes| |yes| |yes| |no| |no| |yes| |no| |no|
|
||||||
|
**get_snmp_information** |no| |no| |no| |no| |no| |no| |no| |yes|
|
||||||
|
**get_ntp_peers** |yes| |yes| |yes| |no| |no| |yes| |no| |yes|
|
||||||
|
**get_interfaces_ip** |yes| |yes| |yes| |no| |no| |yes| |yes| |no|
|
||||||
|
============================== ===== ===== ====== ======= ====== ====== ===== =========
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
call('cli', **{
|
||||||
|
'commands': [
|
||||||
|
"show version",
|
||||||
|
"show chassis fan"
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not NETWORK_DEVICE.get('UP', False):
|
||||||
|
raise Exception('not connected')
|
||||||
|
# if connected will try to execute desired command
|
||||||
|
return {
|
||||||
|
'out': getattr(NETWORK_DEVICE.get('DRIVER'), method)(**params),
|
||||||
|
'result': True,
|
||||||
|
'comment': ''
|
||||||
|
}
|
||||||
|
except Exception as error:
|
||||||
|
# either not connected
|
||||||
|
# either unable to execute the command
|
||||||
|
return {
|
||||||
|
'out': {},
|
||||||
|
'result': False,
|
||||||
|
'comment': 'Cannot execute "{method}" on {device}. Reason: {error}!'.format(
|
||||||
|
device=NETWORK_DEVICE.get('HOSTNAME', '[unspecified hostname]'),
|
||||||
|
method=method,
|
||||||
|
error=error
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user