Skip to content

Commit

Permalink
Merge af8688f into 211db50
Browse files Browse the repository at this point in the history
  • Loading branch information
brigoldberg committed Jul 28, 2016
2 parents 211db50 + af8688f commit ec4a503
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 0 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ recursive-include test *.py
recursive-include test *.text
recursive-include test *.vxlan
recursive-include test *.bgp
recursive-include test *.ospf
recursive-include test *.routemaps
recursive-include test *.varp
recursive-include test *.varp_null
Expand Down
172 changes: 172 additions & 0 deletions pyeapi/api/ospf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#
# Copyright (c) 2014, 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.
#
'''
API module for Ospf
'''

import re

from collections import namedtuple

import netaddr

from pyeapi.api import Entity, EntityCollection
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):
# Returns the OSPF routing configuration as a dict object
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))
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):
match = re.search(r'^router ospf (\d+)', config)
return dict(ospf_process_id=int(match.group(1)))

def _parse_router_id(self, config):
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):
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):
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):
value = 'no shutdown' in config
return dict(shutdown=not value)

def set_shutdown(self):
cmd = 'shutdown'
return self.configure_ospf(cmd)

def set_no_shutdown(self):
cmd = 'no shutdown'
return self.configure_ospf(cmd)

def delete(self):
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):
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):
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)
return self.configure_ospf(cmd)

def add_network(self, network, netmask, area=0):
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):
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):
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):
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):
return Ospf(api)
28 changes: 28 additions & 0 deletions test/fixtures/running_config.ospf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
!
ip routing
!
router ospf 65000
router-id 1.1.1.1
no bfd all-interfaces
distance ospf intra-area 110
distance ospf external 110
distance ospf inter-area 110
redistribute bgp route-map RM-IN
redistribute bgp route-map RM-OUT
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
max-lsa 12000 75 ignore-time 5 ignore-count 5 reset-time 5
adjacency exchange-start threshold 20
log-adjacency-changes
timers throttle spf 0 5000 5000
timers lsa arrival 1000
timers throttle lsa all 1000 5000 5000
no timers out-delay
maximum-paths 128
no timers pacing flood
no max-metric router-lsa
point-to-point routes
no graceful-restart
!
!
94 changes: 94 additions & 0 deletions test/unit/test_api_ospf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import sys
import os
import unittest

sys.path.append(os.path.join(os.path.dirname(__file__), '../lib'))

from testlib import get_fixture, function
from testlib import EapiConfigUnitTest

import pyeapi.api.ospf


class TestApiOspf(EapiConfigUnitTest):

def __init__(self, *args, **kwargs):
super(TestApiOspf, self).__init__(*args, **kwargs)
self.instance = pyeapi.api.ospf.instance(None)
self.config = open(get_fixture('running_config.ospf')).read()

def test_get(self):
result = self.instance.get()
keys = ['networks', 'ospf_process_id', 'redistributions', 'router_id', 'shutdown']
self.assertEqual(sorted(keys), sorted(result.keys()))

def test_create(self):
for ospf_id in ['65000', 65000]:
func = function('create', ospf_id)
cmds = 'router ospf {}'.format(ospf_id)
self.eapi_positive_config_test(func, cmds)

def test_create_invalid_id(self):
for ospf_id in ['66000', 66000]:
with self.assertRaises(ValueError):
self.instance.create(ospf_id)

def test_delete(self):
func = function('delete')
cmds = 'no router ospf 65000'
self.eapi_positive_config_test(func, cmds)

def test_add_network(self):
func = function('add_network', '172.16.10.0', '24', '0')
cmds = ['router ospf 65000', 'network 172.16.10.0/24 area 0']
self.eapi_positive_config_test(func, cmds)

func = function('add_network', '', '24', '0')
self.eapi_exception_config_test(func, ValueError)

func = function('add_network', '172.16.10.0', '', '0')
self.eapi_exception_config_test(func, ValueError)

def test_remove_network(self):
func = function('remove_network', '172.16.10.0', '24', '0')
cmds = ['router ospf 65000', 'no network 172.16.10.0/24 area 0']
self.eapi_positive_config_test(func, cmds)

func = function('remove_network', '', '24', '0')
self.eapi_exception_config_test(func, ValueError)

func = function('remove_network', '172.16.10.0', '', '0')
self.eapi_exception_config_test(func, ValueError)

def test_set_router_id(self):
for state in ['config', 'negate', 'default']:
rid = '1.1.1.1'
if state == 'config':
cmds = ['router ospf 65000', 'router-id 1.1.1.1']
func = function('set_router_id', rid)
elif state == 'negate':
cmds = ['router ospf 65000', 'no router-id']
func = function('set_router_id')
elif state == 'default':
cmds = ['router ospf 65000', 'default router-id']
func = function('set_router_id', rid, True)
self.eapi_positive_config_test(func, cmds)

cmds = ['router ospf 65000', 'no router-id']
func = function('set_router_id')
self.eapi_positive_config_test(func, cmds)

def test_set_shutdown(self):
for state in ['config', 'negate', 'default']:
if state == 'config':
cmds = ['router ospf 65000', 'shutdown']
func = function('set_shutdown')
elif state == 'negate':
cmds = ['router ospf 65000', 'no shutdown']
func = function('set_no_shutdown')
self.eapi_positive_config_test(func, cmds)


if __name__ == '__main__':
unittest.main()

0 comments on commit ec4a503

Please sign in to comment.