Mini Shell
#
# Copyright (c) 2016, Arista Networks, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# Neither the name of Arista Networks nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
"""Module for working with OSPF configuration in EOS
This module provides an API for creating/modifying/deleting
OSPF configurations
"""
import re
from pyeapi.api import Entity
from pyeapi.utils import make_iterable
class Ospf(Entity):
""" The Ospf class implements global Ospf router configuration
"""
def __init__(self, *args, **kwargs):
super(Ospf, self).__init__(*args, **kwargs)
pass
def get(self, vrf=None):
"""Returns the OSPF routing configuration
Args:
vrf (str): VRF name to return OSPF routing config for
Returns:
dict:
keys: router_id (int): OSPF router-id
vrf (str): VRF of the OSPF process
networks (dict): All networks that
are advertised in OSPF
ospf_process_id (int): OSPF proc id
redistribution (dict): All protocols that
are configured to be
redistributed in OSPF
shutdown (bool): Gives the current shutdown
off the process
"""
match = '^router ospf .*'
if vrf:
match += ' vrf %s' % vrf
config = self.get_block(match)
if not config:
return None
response = dict()
response.update(self._parse_router_id(config))
response.update(self._parse_vrf(config))
response.update(self._parse_networks(config))
response.update(self._parse_ospf_process_id(config))
response.update(self._parse_redistribution(config))
response.update(self._parse_shutdown(config))
return response
def _parse_ospf_process_id(self, config):
"""Parses config file for the OSPF proc ID
Args:
config(str): Running configuration
Returns:
dict: key: ospf_process_id (int)
"""
match = re.search(r'^router ospf (\d+)', config)
return dict(ospf_process_id=int(match.group(1)))
def _parse_vrf(self, config):
"""Parses config file for the OSPF vrf name
Args:
config(str): Running configuration
Returns:
dict: key: ospf_vrf (str)
"""
match = re.search(r'^router ospf \d+ vrf (\w+)', config)
if match:
return dict(vrf=match.group(1))
return dict(vrf='default')
def _parse_router_id(self, config):
"""Parses config file for the OSPF router ID
Args:
config(str): Running configuration
Returns:
dict: key: router_id (str)
"""
match = re.search(r'router-id ([^\s]+)', config)
value = match.group(1) if match else None
return dict(router_id=value)
def _parse_networks(self, config):
"""Parses config file for the networks advertised
by the OSPF process
Args:
config(str): Running configuration
Returns:
list: dict:
keys: network (str)
netmask (str)
area (str)
"""
networks = list()
regexp = r'network (.+)/(\d+) area (\d+\.\d+\.\d+\.\d+)'
matches = re.findall(regexp, config)
for (network, netmask, area) in matches:
networks.append(dict(network=network, netmask=netmask, area=area))
return dict(networks=networks)
def _parse_redistribution(self, config):
"""Parses config file for the OSPF router ID
Args:
config (str): Running configuration
Returns:
list: dict:
keys: protocol (str)
route-map (optional) (str)
"""
redistributions = list()
regexp = r'redistribute .*'
matches = re.findall(regexp, config)
for line in matches:
ospf_redist = line.split()
if len(ospf_redist) == 2:
# simple redist: eg 'redistribute bgp'
protocol = ospf_redist[1]
redistributions.append(dict(protocol=protocol))
if len(ospf_redist) == 4:
# complex redist eg 'redistribute bgp route-map NYSE-RP-MAP'
protocol = ospf_redist[1]
route_map_name = ospf_redist[3]
redistributions.append(dict(protocol=protocol,
route_map=route_map_name))
return dict(redistributions=redistributions)
def _parse_shutdown(self, config):
"""Parses config file for the OSPF router ID
Args:
config(str): Running configuration
Returns:
dict: key: shutdown (bool)
"""
value = 'no shutdown' in config
return dict(shutdown=not value)
def set_shutdown(self):
"""Shutdowns the OSPF process
Args:
None
Returns:
bool: True if the commands are completed successfully
"""
cmd = 'shutdown'
return self.configure_ospf(cmd)
def set_no_shutdown(self):
"""Removes the shutdown property from the OSPF process
Args:
None
Returns:
bool: True if the commands are completed successfully
"""
cmd = 'no shutdown'
return self.configure_ospf(cmd)
def delete(self):
"""Removes the entire ospf process from the running configuration
Args:
None
Returns:
bool: True if the command completed succssfully
"""
config = self.get()
if not config:
return True
command = 'no router ospf {}'.format(config['ospf_process_id'])
return self.configure(command)
def create(self, ospf_process_id, vrf=None):
"""Creates a OSPF process in the specified VRF or the default VRF.
Args:
ospf_process_id (str): The OSPF process Id value
vrf (str): The VRF to apply this OSPF process to
Returns:
bool: True if the command completed successfully
Exception:
ValueError: If the ospf_process_id passed in less
than 0 or greater than 65536
"""
value = int(ospf_process_id)
if not 0 < value < 65536:
raise ValueError('ospf as must be between 1 and 65535')
command = 'router ospf {}'.format(ospf_process_id)
if vrf:
command += ' vrf %s' % vrf
return self.configure(command)
def configure_ospf(self, cmd):
"""Allows for a list of OSPF subcommands to be configured"
Args:
cmd: (list or str): Subcommand to be entered
Returns:
bool: True if all the commands completed successfully
"""
config = self.get()
cmds = ['router ospf {}'.format(config['ospf_process_id'])]
cmds.extend(make_iterable(cmd))
return super(Ospf, self).configure(cmds)
def set_router_id(self, value=None, default=False, disable=False):
"""Controls the router id property for the OSPF Proccess
Args:
value (str): The router-id value
default (bool): Controls the use of the default keyword
disable (bool): Controls the use of the no keyword
Returns:
bool: True if the commands are completed successfully
"""
cmd = self.command_builder('router-id', value=value,
default=default, disable=disable)
return self.configure_ospf(cmd)
def add_network(self, network, netmask, area=0):
"""Adds a network to be advertised by OSPF
Args:
network (str): The network to be advertised in dotted decimal
notation
netmask (str): The netmask to configure
area (str): The area the network belongs to.
By default this value is 0
Returns:
bool: True if the command completes successfully
Exception:
ValueError: This will get raised if network or netmask
are not passed to the method
"""
if network == '' or netmask == '':
raise ValueError('network and mask values '
'may not be empty')
cmd = 'network {}/{} area {}'.format(network, netmask, area)
return self.configure_ospf(cmd)
def remove_network(self, network, netmask, area=0):
"""Removes a network advertisment by OSPF
Args:
network (str): The network to be removed in dotted decimal
notation
netmask (str): The netmask to configure
area (str): The area the network belongs to.
By default this value is 0
Returns:
bool: True if the command completes successfully
Exception:
ValueError: This will get raised if network or netmask
are not passed to the method
"""
if network == '' or netmask == '':
raise ValueError('network and mask values '
'may not be empty')
cmd = 'no network {}/{} area {}'.format(network, netmask, area)
return self.configure_ospf(cmd)
def add_redistribution(self, protocol, route_map_name=None):
"""Adds a protocol redistribution to OSPF
Args:
protocol (str): protocol to redistribute
route_map_name (str): route-map to be used to
filter the protocols
Returns:
bool: True if the command completes successfully
Exception:
ValueError: This will be raised if the protocol pass is not one
of the following: [rip, bgp, static, connected]
"""
protocols = ['bgp', 'rip', 'static', 'connected']
if protocol not in protocols:
raise ValueError('redistributed protocol must be'
'bgp, connected, rip or static')
if route_map_name is None:
cmd = 'redistribute {}'.format(protocol)
else:
cmd = 'redistribute {} route-map {}'.format(protocol,
route_map_name)
return self.configure_ospf(cmd)
def remove_redistribution(self, protocol):
"""Removes a protocol redistribution to OSPF
Args:
protocol (str): protocol to redistribute
route_map_name (str): route-map to be used to
filter the protocols
Returns:
bool: True if the command completes successfully
Exception:
ValueError: This will be raised if the protocol pass is not one
of the following: [rip, bgp, static, connected]
"""
protocols = ['bgp', 'rip', 'static', 'connected']
if protocol not in protocols:
raise ValueError('redistributed protocol must be'
'bgp, connected, rip or static')
cmd = 'no redistribute {}'.format(protocol)
return self.configure_ospf(cmd)
def instance(api):
"""Returns an instance of Ospf
"""
return Ospf(api)
Zerion Mini Shell 1.0