Page MenuHomeVyOS Platform

frr.py
No OneTemporary

Size
9 KB
Referenced Files
None
Subscribers
None
# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
r"""
A Library for interracting with the FRR daemon suite.
It supports simple configuration manipulation and loading using the official tools
supplied with FRR (vtysh and frr-reload)
All configuration management and manipulation is done using strings and regex.
Example Usage
#####
# Reading configuration from frr:
```
>>> original_config = get_configuration()
>>> repr(original_config)
'!\nfrr version 7.3.1\nfrr defaults traditional\nhostname debian\n......
```
# Modify a configuration section:
```
>>> new_bgp_section = 'router bgp 65000\n neighbor 192.0.2.1 remote-as 65000\n'
>>> modified_config = replace_section(original_config, new_bgp_section, replace_re=r'router bgp \d+')
>>> repr(modified_config)
'............router bgp 65000\n neighbor 192.0.2.1 remote-as 65000\n...........'
```
Remove a configuration section:
```
>>> modified_config = remove_section(original_config, r'router ospf')
```
Test the new configuration:
```
>>> try:
>>> mark_configuration(modified configuration)
>>> except ConfigurationNotValid as e:
>>> print('resulting configuration is not valid')
>>> sys.exit(1)
```
Apply the new configuration:
```
>>> try:
>>> replace_configuration(modified_config)
>>> except CommitError as e:
>>> print('Exception while commiting the supplied configuration')
>>> print(e)
>>> exit(1)
```
"""
import tempfile
import re
from vyos import util
_frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd',
'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd']
path_vtysh = '/usr/bin/vtysh'
path_frr_reload = '/usr/lib/frr/frr-reload.py'
class FrrError(Exception):
pass
class ConfigurationNotValid(FrrError):
"""
The configuratioin supplied to vtysh is not valid
"""
pass
class CommitError(FrrError):
"""
Commiting the supplied configuration failed to commit by a unknown reason
see commit error and/or run mark_configuration on the specified configuration
to se error generated
used by: reload_configuration()
"""
pass
class ConfigSectionNotFound(FrrError):
"""
Removal of configuration failed because it is not existing in the supplied configuration
"""
pass
def get_configuration(daemon=None, marked=False):
""" Get current running FRR configuration
daemon: Collect only configuration for the specified FRR daemon,
supplying daemon=None retrieves the complete configuration
marked: Mark the configuration with "end" tags
return: string containing the running configuration from frr
"""
if daemon and daemon not in _frr_daemons:
raise ValueError(f'The specified daemon type is not supported {repr(daemon)}')
cmd = f"{path_vtysh} -c 'show run'"
if daemon:
cmd += f' -d {daemon}'
output, code = util.popen(cmd, stderr=util.STDOUT)
if code:
raise OSError(code, output)
config = output.replace('\r', '')
# Remove first header lines from FRR config
config = config.split("\n", 3)[-1]
# Mark the configuration with end tags
if marked:
config = mark_configuration(config)
return config
def mark_configuration(config):
""" Add end marks and Test the configuration for syntax faults
If the configuration is valid a marked version of the configuration is returned,
or else it failes with a ConfigurationNotValid Exception
config: The configuration string to mark/test
return: The marked configuration from FRR
"""
output, code = util.popen(f"{path_vtysh} -m -f -", stderr=util.STDOUT, input=config)
if code == 2:
raise ConfigurationNotValid(str(output))
elif code:
raise OSError(code, output)
config = output.replace('\r', '')
return config
def reload_configuration(config, daemon=None):
""" Execute frr-reload with the new configuration
This will try to reapply the supplied configuration inside FRR.
The configuration needs to be a complete configuration from the integrated config or
from a daemon.
config: The configuration to apply
daemon: Apply the conigutaion to the specified FRR daemon,
supplying daemon=None applies to the integrated configuration
return: None
"""
if daemon and daemon not in _frr_daemons:
raise ValueError(f'The specified daemon type is not supported {repr(daemon)}')
f = tempfile.NamedTemporaryFile('w')
f.write(config)
f.flush()
cmd = f'{path_frr_reload} --reload'
if daemon:
cmd += f' --daemon {daemon}'
cmd += f' {f.name}'
output, code = util.popen(cmd, stderr=util.STDOUT)
f.close()
if code == 1:
raise CommitError(f'Configuration FRR failed while commiting code: {repr(output)}')
elif code:
raise OSError(code, output)
return output
def execute(command):
""" Run commands inside vtysh
command: str containing commands to execute inside a vtysh session
"""
if not isinstance(command, str):
raise ValueError(f'command needs to be a string: {repr(command)}')
cmd = f"{path_vtysh} -c '{command}'"
output, code = util.popen(cmd, stderr=util.STDOUT)
if code:
raise OSError(code, output)
config = output.replace('\r', '')
return config
def configure(lines, daemon=False):
""" run commands inside config mode vtysh
lines: list or str conaining commands to execute inside a configure session
only one command executed on each configure()
Executing commands inside a subcontext uses the list to describe the context
ex: ['router bgp 6500', 'neighbor 192.0.2.1 remote-as 65000']
return: None
"""
if isinstance(lines, str):
lines = [lines]
elif not isinstance(lines, list):
raise ValueError('lines needs to be string or list of commands')
if daemon and daemon not in _frr_daemons:
raise ValueError(f'The specified daemon type is not supported {repr(daemon)}')
cmd = f'{path_vtysh}'
if daemon:
cmd += f' -d {daemon}'
cmd += " -c 'configure terminal'"
for x in lines:
cmd += f" -c '{x}'"
output, code = util.popen(cmd, stderr=util.STDOUT)
if code == 1:
raise ConfigurationNotValid(f'Configuration FRR failed: {repr(output)}')
elif code:
raise OSError(code, output)
config = output.replace('\r', '')
return config
def _replace_section(config, replacement, replace_re, before_re):
r"""Replace a section of FRR config
config: full original configuration
replacement: replacement configuration section
replace_re: The regex to replace
example: ^router bgp \d+$.?*^!$
this will replace everything between ^router bgp X$ and ^!$
before_re: When replace_re is not existant, the config will be added before this tag
example: ^line vty$
return: modified configuration as a text file
"""
# Check if block is configured, remove the existing instance else add a new one
if re.findall(replace_re, config, flags=re.MULTILINE | re.DOTALL):
# Section is in the configration, replace it
return re.sub(replace_re, replacement, config, count=1,
flags=re.MULTILINE | re.DOTALL)
if before_re:
if not re.findall(before_re, config, flags=re.MULTILINE | re.DOTALL):
raise ConfigSectionNotFound(f"Config section {before_re} not found in config")
# If no section is in the configuration, add it before the line vty line
return re.sub(before_re, rf'{replacement}\n\g<1>', config, count=1,
flags=re.MULTILINE | re.DOTALL)
raise ConfigSectionNotFound(f"Config section {replacement} not found in config")
def replace_section(config, replacement, from_re, to_re=r'!', before_re=r'line vty'):
r"""Replace a section of FRR config
config: full original configuration
replacement: replacement configuration section
from_re: Regex for the start of section matching
example: 'router bgp \d+'
to_re: Regex for stop of section matching
default: '!'
example: '!' or 'end'
before_re: When from_re/to_re does not return a match, the config will
be added before this tag
default: ^line vty$
startline and endline tags will be automatically added to the resulting from_re/to_re and before_re regex'es
"""
return _replace_section(config, replacement, replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=rf'^{before_re}$')
def remove_section(config, from_re, to_re='!'):
return _replace_section(config, '', replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=None)

File Metadata

Mime Type
text/x-script.python
Expires
Sat, Oct 17, 12:10 AM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4352831
Default Alt Text
frr.py (9 KB)

Event Timeline