Created
March 21, 2019 03:38
-
-
Save vnetman/becf9a989fe06053af38e8d6032e26e1 to your computer and use it in GitHub Desktop.
Netmiko script to place an interface in a VLAN based on its description
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import sys | |
import re | |
import logging | |
import ipaddress | |
import netrc | |
from netmiko import Netmiko | |
# Netmiko script to place different interfaces in different VLANs based | |
# on the interface descriptions | |
# Login credentials are stored in ~/.netrc | |
# | |
# .netrc entries look like this: | |
# | |
# machine 192.168.1.110 login my_login_id password l0gin_pa$$w0rd account ena6le_pa$$w0rd | |
# machine access_switch login my_login_id password l0gin_pa$$w0rd | |
# | |
# In the first example above, the machine is specified by its IP address. | |
# The enable password is given against the 'account' field (which is really a | |
# hack). | |
# In the second example above, the machine is specified by its name, and it | |
# has no enable password (hence the absence of the 'account' field). | |
# | |
# Ensure .netrc is protected by appropriate r/w permissions | |
DESIRED_SETTINGS = { | |
# This is the device to be configured. Credentials must be specified in | |
# ~/.netrc as described above | |
'device' : '192.168.1.110', | |
# Map interface descriptions to VLAN Ids | |
'desc_to_vlan_map': {'vendor lab' : 119, | |
'hardware lab': 120, | |
'sensors' : 121, | |
'lobby' : 122}} | |
def main(): | |
'''Main program logic | |
''' | |
logging.basicConfig(level=logging.INFO, # Change INFO to DEBUG for debugging | |
format='%(asctime)s %(levelname)s: %(message)s', | |
stream=sys.stdout) | |
device = make_connection_to_device() | |
# Get the list of interfaces currently configured | |
current_interface_list = get_interface_list(device) | |
if not current_interface_list: | |
print('Failed to read current interface list, aborting', | |
file=sys.stderr) | |
device.disconnect() | |
sys.exit(-1) | |
# The list of commands to be pushed | |
cfg_commands = [] | |
for this_if in current_interface_list: | |
logging.debug('interface found: %s', this_if) | |
this_if_cfg = get_current_interface_running_config(device, this_if) | |
for this_cfg_line in this_if_cfg: | |
if not this_cfg_line.startswith(' description'): | |
# uninteresting line | |
continue | |
logging.debug('\"%s\" is a description line', this_cfg_line) | |
for pattern in DESIRED_SETTINGS['desc_to_vlan_map']: | |
vlan_id = DESIRED_SETTINGS['desc_to_vlan_map'][pattern] | |
if (' description ' + pattern).lower() != this_cfg_line.lower(): | |
# description doesn't match this pattern | |
continue | |
logging.info('Will place %s in vlan %d because of description ' | |
'match \"%s\"', this_if, vlan_id, pattern) | |
cfg_commands.append('interface ' + this_if) | |
cfg_commands.append(' switchport access vlan {}'.format(vlan_id)) | |
cfg_commands.append(' switchport mode access') | |
print('The following configuration will be applied:') | |
print('------------------------------') | |
for _ in cfg_commands: | |
print(_) | |
print('------------------------------') | |
config_result = device.send_config_set(cfg_commands) | |
print('Configuration result:') | |
print('------------------------------') | |
print(config_result) | |
print('------------------------------') | |
print('Done, disconnecting from device.') | |
device.disconnect() | |
sys.exit(0) | |
#--- | |
def get_interface_list(device): | |
'''Return a list of currently configured interfaces on the device | |
''' | |
# Lines in the 'show interface summary' look like this: | |
# | |
# *: interface is up | |
# IHQ: pkts in input hold queue IQD: pkts dropped from input queue | |
# OHQ: pkts in output hold queue OQD: pkts dropped from output queue | |
# RXBS: rx rate (bits/sec) RXPS: rx rate (pkts/sec) | |
# TXBS: tx rate (bits/sec) TXPS: tx rate (pkts/sec) | |
# TRTL: throttle count | |
# | |
# Interface IHQ IQD OHQ OQD RXBS RXPS TXBS TXPS TRTL | |
#------------------------------------------------------------------------- | |
#* Vlan1 0 0 0 0 0 0 0 0 0 | |
#* FastEthernet0/1 0 0 0 0 0 0 0 0 0 | |
# FastEthernet0/2 0 0 0 0 0 0 0 0 0 | |
# regex to match the header separator | |
re_header_separator = re.compile(r'^-{50}.*$') # At least 50 '-' characters | |
# regex to extract the interface name | |
re_intf_line = re.compile(r'''^ # start of line | |
\*? # there may be a '*' char | |
\s+ # one or more spaces | |
([^\s]+)\s # All nonspace chars until a space | |
.*$''', # Ignore the rest of the line | |
re.VERBOSE) | |
# beginning of the string | |
# there may be a single '*' | |
# one or more spaces | |
# grab everything that's not a space, followed by a space | |
# don't care about anything after that | |
# till the end of the string | |
interface_list = [] | |
header_finished = False | |
sh_op = device.send_command('show interface summary') | |
for line in sh_op.split('\n'): | |
# Ignore all lines until we see the '------' header separator | |
if not header_finished: | |
match_obj = re_header_separator.search(line) | |
if match_obj: | |
header_finished = True | |
continue | |
match_obj = re_intf_line.search(line) | |
if match_obj: | |
interface_list.append(match_obj.group(1)) | |
return interface_list | |
#--- | |
def get_current_interface_running_config(device, interface): | |
'''Return a list of lines containing the given interface's running | |
configuration''' | |
interface_config = [] | |
sh_cmd_op = device.send_command('show run interface {}'.format(interface)) | |
for line in sh_cmd_op.split('\n'): | |
# Ignore uninteresting lines | |
if not line: | |
continue | |
if line.startswith('Building configuration...'): | |
continue | |
if line.startswith('Current configuration'): | |
continue | |
if line == '!': | |
continue | |
if line == 'end': | |
break | |
interface_config.append(line) | |
return interface_config | |
#--- | |
def make_connection_to_device(): | |
'''Helper invoked from main() to set up a netmiko connection to the | |
device, and put it into enable mode''' | |
# access the netrc to read the credentials | |
try: | |
rc = netrc.netrc() | |
except FileNotFoundError as e: | |
print('(Failed to access netrc file for gathering ', | |
'login credentials: {})'.format(str(e)), file=sys.stderr) | |
sys.exit(-1) | |
netmiko_device_info = {} | |
netmiko_device_info['host'] = DESIRED_SETTINGS['device'] | |
netmiko_device_info['device_type'] = 'cisco_ios' | |
try: | |
host = netmiko_device_info['host'] | |
(login, enable_password, password) = rc.authenticators(host) | |
except TypeError: | |
print('No entry in netrc file for device "{}", and no default ' | |
'either.'.format(netmiko_device_info['host']), file=sys.stderr) | |
sys.exit(-1) | |
# Fill in the user name / password / enable password device_info | |
# attributes from the info we read from .netrc | |
netmiko_device_info['username'] = login | |
netmiko_device_info['password'] = password | |
if enable_password: | |
netmiko_device_info['secret'] = enable_password | |
print('Connecting to device_info "{}"...'.format( | |
netmiko_device_info['host']), end='', flush=True) | |
device = Netmiko(**netmiko_device_info) | |
print('connected.') | |
print('Entering enable mode...', end='', flush=True) | |
device.enable() | |
print('done.') | |
prompt = device.find_prompt() | |
print('Prompt is "{}"'.format(prompt)) | |
return device | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment