Proxy Minion for network devices using Napalm library

This commit is contained in:
Mircea Ulinic 2016-02-23 12:44:08 +00:00 committed by Jerome Fleury
parent f833b6f5b6
commit 8ad5794b60
6 changed files with 501 additions and 0 deletions

View File

@ -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

View File

@ -0,0 +1,6 @@
salt.modules.napalm_network module
===============================
.. automodule:: salt.modules.napalm_network
:members:
:undoc-members:

View File

@ -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

View File

@ -0,0 +1,6 @@
================
salt.proxy.napalm
================
.. automodule:: salt.proxy.napalm
:members:

View 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
View 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
)
}