-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
295 additions
and
0 deletions.
There are no files selected for viewing
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
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
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) |
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
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 | ||
! | ||
! |
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
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() | ||
|