Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ branch = True
omit = *mock*
*test*
*netaddr*
*site-packages*

[report]
# Regexes for lines to exclude from consideration
Expand All @@ -23,6 +24,8 @@ exclude_lines =
if 0:
if __name__ == .__main__.:

show_missing = True

ignore_errors = True

[html]
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# make unittest -- runs the unit tests
# make systest -- runs the system tests
# make clean -- clean distutils
# make coverage_report -- code coverage report
#
########################################################
# variable section
Expand Down Expand Up @@ -67,4 +68,4 @@ systest: clean
$(COVERAGE) run -m unittest discover test/system -v

coverage_report:
$(COVERAGE) report -m
$(COVERAGE) report --rcfile=".coveragerc"
202 changes: 184 additions & 18 deletions pyeapi/api/ospf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2014, Arista Networks, Inc.
# Copyright (c) 2016, Arista Networks, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -29,33 +29,47 @@
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
'''
API module for Ospf
'''
"""Module for working with OSPF configuration in EOS

import re

from collections import namedtuple
This module provides an API for creating/modifying/deleting
OSPF configurations

import netaddr
"""

from pyeapi.api import Entity, EntityCollection
import re
from pyeapi.api import Entity
from pyeapi.utils import make_iterable


class Ospf(Entity):
# The Ospf class implements global Ospf router configuration
""" The Ospf class implements global Ospf router configuration
"""

def __init__(self, *args, **kwargs):
super(Ospf, self).__init__(*args, **kwargs)
pass

def get(self):
# Returns the OSPF routing configuration as a dict object
"""Returns the OSPF routing configuration

Args:
None
Returns:
dict:
keys: router_id (int): OSPF router-id
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
"""

config = self.get_block('^router ospf .*')
if not config:
return None

response = dict()
response.update(self._parse_router_id(config))
response.update(self._parse_networks(config))
Expand All @@ -66,15 +80,41 @@ def get(self):
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_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)
Expand All @@ -83,6 +123,15 @@ def _parse_networks(self, config):
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)
Expand All @@ -96,77 +145,194 @@ def _parse_redistribution(self, config):
# 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))
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):
"""Creates a OSPF process in the default VRF

Args:
ospf_process_id (str): The OSPF proccess Id value
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)
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):
cmd = self.command_builder('router-id', value=value, default=default, disable=disable)
"""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)
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)
cmd = 'no redistribute {}'.format(protocol)
return self.configure_ospf(cmd)

def instance(api):
"""Returns an instance of Ospf
"""
return Ospf(api)
1 change: 1 addition & 0 deletions test/fixtures/running_config.ospf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ router ospf 65000
distance ospf inter-area 110
redistribute bgp route-map RM-IN
redistribute bgp route-map RM-OUT
redistribute static
area 0.0.0.0 default-cost 10
network 172.16.10.0/24 area 0.0.0.0
network 172.17.0.0/16 area 0.0.0.0
Expand Down
Loading