Skip to content

Commit

Permalink
Adding a couple of actors to help migrate quagga to frr (#467)
Browse files Browse the repository at this point in the history
Migrate Quagga to FRR

Quagga has been replaced by FRR on RHEL 8 systems:
    https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/setting-your-routing-protocols_configuring-and-managing-networking

Actors automatically migrate the Quagga setup to FRR, except on systems using
the babeld daemon which has been dropped from FRR because of licensing issues.
If the use of the babeld daemon is detected, IPU is inhibited and user
is notified to reconfigure the system to use otherservices instead.
  • Loading branch information
mruprich authored Oct 25, 2020
1 parent f58643f commit fdb9916
Show file tree
Hide file tree
Showing 11 changed files with 482 additions and 0 deletions.
24 changes: 24 additions & 0 deletions repos/system_upgrade/el7toel8/actors/quaggadaemons/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from leapp.actors import Actor
from leapp.libraries.actor.quaggadaemons import process_daemons
from leapp.libraries.common.rpms import has_package
from leapp.models import InstalledRedHatSignedRPM, QuaggaToFrrFacts
from leapp.tags import FactsPhaseTag, IPUWorkflowTag


class QuaggaDaemons(Actor):
"""
Active quagga daemons check.
Checking for daemons that are currently running in the system.
These should be enabled in /etc/frr/daemons later in the process.
The tools will check for config files later on since these should stay in the system.
"""

name = 'quagga_daemons'
consumes = (InstalledRedHatSignedRPM,)
produces = (QuaggaToFrrFacts,)
tags = (FactsPhaseTag, IPUWorkflowTag)

def process(self):
if has_package(InstalledRedHatSignedRPM, 'quagga'):
self.produce(process_daemons())
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from leapp.libraries.stdlib import CalledProcessError, api, run
from leapp.models import QuaggaToFrrFacts

QUAGGA_DAEMONS = [
'babeld',
'bgpd',
'isisd',
'ospf6d',
'ospfd',
'ripd',
'ripngd',
'zebra'
]


def _check_service(name, state):
try:
run(['systemctl', 'is-{}'.format(state), name])
api.current_logger().debug('%s is %s', name, state)
except CalledProcessError:
api.current_logger().debug('%s is not %s', name, state)
return False

return True


def process_daemons():
active_daemons = [daemon for daemon in QUAGGA_DAEMONS if _check_service(daemon, 'active')]
enabled_daemons = [daemon for daemon in QUAGGA_DAEMONS if _check_service(daemon, 'enabled')]

if active_daemons:
api.current_logger().debug('active quaggadaemons: %s', ', '.join(active_daemons))

if enabled_daemons:
api.current_logger().debug('enabled quaggadaemons: %s', ', '.join(enabled_daemons))

return QuaggaToFrrFacts(active_daemons=active_daemons, enabled_daemons=enabled_daemons)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from leapp.libraries.actor import quaggadaemons
from leapp.models import QuaggaToFrrFacts

# daemons for mocked _check_service function
TEST_DAEMONS = ['bgpd', 'ospfd', 'zebra']


def mock_check_service(name, state):
if name in TEST_DAEMONS:
return True

return False


def test_process_daemons():
quaggadaemons._check_service = mock_check_service

facts = quaggadaemons.process_daemons()
assert isinstance(facts, QuaggaToFrrFacts)
assert facts.active_daemons == TEST_DAEMONS
assert facts.enabled_daemons == TEST_DAEMONS
50 changes: 50 additions & 0 deletions repos/system_upgrade/el7toel8/actors/quaggareport/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from leapp import reporting
from leapp.actors import Actor
from leapp.models import QuaggaToFrrFacts, Report
from leapp.reporting import create_report
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag

COMMON_REPORT_TAGS = [
reporting.Tags.NETWORK,
reporting.Tags.SERVICES
]


class QuaggaReport(Actor):
"""
Checking for babeld on RHEL-7.
This actor is supposed to report that babeld was used on RHEL-7
and it is no longer available in RHEL-8.
"""

name = 'quagga_report'
consumes = (QuaggaToFrrFacts, )
produces = (Report, )
tags = (ChecksPhaseTag, IPUWorkflowTag)

def process(self):
try:
quagga_facts = next(self.consume(QuaggaToFrrFacts))
except StopIteration:
return
if 'babeld' in quagga_facts.active_daemons or 'babeld' in quagga_facts.enabled_daemons:
create_report([
reporting.Title('Babeld is not available in FRR'),
reporting.ExternalLink(
url='https://access.redhat.com/'
'documentation/en-us/red_hat_enterprise_linux/8/html/'
'configuring_and_managing_networking/setting-your-rou'
'ting-protocols_configuring-and-managing-networking',
title='Setting routing protocols in RHEL8'),
reporting.Summary(
'babeld daemon which was a part of quagga implementation in RHEL7 '
'is not available in RHEL8 in FRR due to licensing issues.'
),
reporting.Severity(reporting.Severity.HIGH),
reporting.Tags(COMMON_REPORT_TAGS),
reporting.Flags([reporting.Flags.INHIBITOR]),
reporting.Remediation(hint='Please use RIP, OSPF or EIGRP instead of Babel')
])
else:
self.log.debug('babeld not used, moving on.')
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import pytest

from leapp.models import QuaggaToFrrFacts
from leapp.snactor.fixture import ActorContext


# TODO We can't use caplog here as logs from other processes is
# hard to capture and caplog not see it.
@pytest.mark.parametrize(
("quagga_facts", "active_daemons", "has_report", "msg_in_log"),
[
(True, ["babeld"], True, None),
(True, ["something_else"], False, "babeld not used, moving on"),
(False, [], False, None),
],
)
def test_quaggareport(
monkeypatch,
current_actor_context,
quagga_facts,
active_daemons,
has_report,
msg_in_log,
):
"""Test quaggareport.
:type current_actor_context:ActorContext
"""
if quagga_facts:
current_actor_context.feed(
QuaggaToFrrFacts(
active_daemons=active_daemons,
enabled_daemons=["bgpd", "ospfd", "zebra"],
)
)
current_actor_context.run()
if has_report:
assert current_actor_context.messages()[0]["type"] == "Report"
if msg_in_log:
assert not current_actor_context.messages()
23 changes: 23 additions & 0 deletions repos/system_upgrade/el7toel8/actors/quaggatofrr/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from leapp.actors import Actor
from leapp.libraries.actor.quaggatofrr import process_facts
from leapp.models import QuaggaToFrrFacts
from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag


class QuaggaToFrr(Actor):
"""
Edit frr configuration on the new system.
Take gathered info about quagga from RHEL 7 and apply these to frr in RHEL 8.
"""

name = 'quagga_to_frr'
consumes = (QuaggaToFrrFacts, )
produces = ()
tags = (ApplicationsPhaseTag, IPUWorkflowTag)

def process(self):
quagga_facts = next(self.consume(QuaggaToFrrFacts), None)

if quagga_facts:
process_facts(quagga_facts)
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import os
import re
import shutil

from leapp.libraries.stdlib import CalledProcessError, api, run

DAEMON_FILE = '/etc/frr/daemons'
# if this file sitll exists after the removal of quagga, it has been modified
CONFIG_FILE = '/etc/sysconfig/quagga.rpmsave'
QUAGGA_CONF_FILES = '/etc/quagga/'
FRR_CONF_FILES = '/etc/frr/'

regex = re.compile(r'\w+(?<!WATCH)(?<!BABELD)_OPTS=".*"')


def _get_config_data(path):
conf_data = {}
with open(path) as f:
for line in f:
if regex.match(line):
k, v = line.rstrip().split("=")
conf_data[k.split("_")[0].lower()] = v.strip('"')

return conf_data


def _edit_new_config(path, active_daemons, config_data):
with open(path, 'r') as f:
data = f.read()

# replace no as yes in /etc/frr/daemons
for daemon in active_daemons:
data = re.sub(r'{}=no'.format(daemon), r'{}=yes'.format(daemon), data, flags=re.MULTILINE)

if config_data:
for daemon in config_data:
data = re.sub(r'{}_options=\(".*"\)'.format(daemon),
r'{}_options=("{}")'.format(daemon, config_data[daemon]),
data, flags=re.MULTILINE)

return data


# 1. parse /etc/sysconfig/quagga.rpmsave if it exists
# 2. change =no to =yes with every enabled daemon
# 3. use data from data from quagga.rpmsave in new daemon file
def _change_config(quagga_facts):
config_data = {}
if os.path.isfile(CONFIG_FILE):
config_data = _get_config_data(CONFIG_FILE)

# This file should definitely exist, if not, something went wrong with the upgrade
if os.path.isfile(DAEMON_FILE):
data = _edit_new_config(DAEMON_FILE, quagga_facts.active_daemons, config_data)
with open(DAEMON_FILE, 'w') as f:
f.write(data)


# In quagga, each daemon needed to be started individually
# In frr, only frr is started as a daemon, the rest is started based on the daemons file
# So as long as at least one daemon was active in quagga, frr should be enabled
def _enable_frr(quagga_facts):
# remove babeld?
if quagga_facts.enabled_daemons:
try:
run(['systemctl', 'enable', 'frr'])
except CalledProcessError:
return False

return True


# due to an error in quagga, the conf files are not deleted after uninstall
# we can copy them as they are
def _copy_config_files(src_path, dest_path):
conf_files = os.listdir(src_path)
for file_name in conf_files:
full_path = os.path.join(src_path, file_name)
if os.path.isfile(full_path):
shutil.copy(full_path, dest_path)
api.current_logger().debug('Copying %s to %s%s', full_path, dest_path, file_name)


def process_facts(quagga_facts):
_change_config(quagga_facts)
_copy_config_files(QUAGGA_CONF_FILES, FRR_CONF_FILES)
_enable_frr(quagga_facts)
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# This file tells the frr package which daemons to start.
#
# Entries are in the format: <daemon>=(yes|no|priority)
# 0, "no" = disabled
# 1, "yes" = highest priority
# 2 .. 10 = lower priorities
#
# For daemons which support multiple instances, a 2nd line listing
# the instances can be added. Eg for ospfd:
# ospfd=yes
# ospfd_instances="1,2"
#
# Priorities were suggested by Dancer <[email protected]>.
# They're used to start the FRR daemons in more than one step
# (for example start one or two at network initialization and the
# rest later). The number of FRR daemons being small, priorities
# must be between 1 and 9, inclusive (or the initscript has to be
# changed). /etc/init.d/frr then can be started as
#
# /etc/init.d/frr <start|stop|restart|<priority>>
#
# where priority 0 is the same as 'stop', priority 10 or 'start'
# means 'start all'
#
# Sample configurations for these daemons can be found in
# /usr/share/doc/frr/examples/.
#
# ATTENTION:
#
# When activation a daemon at the first time, a config file, even if it is
# empty, has to be present *and* be owned by the user and group "frr", else
# the daemon will not be started by /etc/init.d/frr. The permissions should
# be u=rw,g=r,o=.
# When using "vtysh" such a config file is also needed. It should be owned by
# group "frrvty" and set to ug=rw,o= though. Check /etc/pam.d/frr, too.
#
watchfrr_enable=yes
watchfrr_options="-r '/usr/lib/frr/frr restart %s' -s '/usr/lib/frr/frr start %s' -k '/usr/lib/frr/frr stop %s'"
#
zebra=no
bgpd=no
ospfd=no
ospf6d=no
ripd=no
ripngd=no
isisd=no
pimd=no
nhrpd=no
eigrpd=no
sharpd=no
pbrd=no
staticd=no
bfdd=no
fabricd=no

#
# Command line options for the daemons
#
zebra_options=("-A 127.0.0.1")
bgpd_options=("-A 127.0.0.1")
ospfd_options=("-A 127.0.0.1")
ospf6d_options=("-A ::1")
ripd_options=("-A 127.0.0.1")
ripngd_options=("-A ::1")
isisd_options=("-A 127.0.0.1")
pimd_options=("-A 127.0.0.1")
nhrpd_options=("-A 127.0.0.1")
eigrpd_options=("-A 127.0.0.1")
sharpd_options=("-A 127.0.0.1")
pbrd_options=("-A 127.0.0.1")
staticd_options=("-A 127.0.0.1")
bfdd_options=("-A 127.0.0.1")
fabricd_options=("-A 127.0.0.1")

#
# If the vtysh_enable is yes, then the unified config is read
# and applied if it exists. If no unified frr.conf exists
# then the per-daemon <daemon>.conf files are used)
# If vtysh_enable is no or non-existant, the frr.conf is ignored.
# it is highly suggested to have this set to yes
vtysh_enable=yes

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#
# Default: Bind all daemon vtys to the loopback(s) only
#
BABELD_OPTS="--daemon -A 192.168.100.1"
BGPD_OPTS="--daemon -A 10.10.100.1"
ISISD_OPTS="--daemon -A ::1"
OSPF6D_OPTS="-A ::1"
OSPFD_OPTS="-A 127.0.0.1"
RIPD_OPTS="-A 127.0.0.1"
RIPNGD_OPTS="-A ::1"
ZEBRA_OPTS="-s 90000000 --daemon -A 127.0.0.1"

# Watchquagga configuration for LSB initscripts
#
# (Not needed with systemd: the service files are configured to automatically
# restart any daemon on failure. If zebra fails, all running daemons will be
# stopped; zebra will be started again; and then the previously running daemons
# will be started again.)
#
# Uncomment and edit this line to reflect the daemons you are actually using:
#WATCH_DAEMONS="zebra bgpd ospfd ospf6d ripd ripngd"
#
# Timer values can be adjusting by editing this line:
WATCH_OPTS="-Az -b_ -r/sbin/service_%s_restart -s/sbin/service_%s_start -k/sbin/service_%s_stop"
Loading

0 comments on commit fdb9916

Please sign in to comment.