initial work to add support for quagga services as config services
This commit is contained in:
parent
8f03c9c975
commit
422bf9ac15
7 changed files with 312 additions and 65 deletions
|
@ -42,6 +42,10 @@ class ConfigService(abc.ABC):
|
||||||
configs = self.default_configs[:]
|
configs = self.default_configs[:]
|
||||||
self._define_config(configs)
|
self._define_config(configs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def clean_text(text: str) -> str:
|
||||||
|
return inspect.cleandoc(text)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
|
@ -147,12 +151,12 @@ class ConfigService(abc.ABC):
|
||||||
basename = pathlib.Path(name).name
|
basename = pathlib.Path(name).name
|
||||||
if name in self.custom_templates:
|
if name in self.custom_templates:
|
||||||
template = self.custom_templates[name]
|
template = self.custom_templates[name]
|
||||||
template = inspect.cleandoc(template)
|
template = self.clean_text(template)
|
||||||
elif self.templates.has_template(basename):
|
elif self.templates.has_template(basename):
|
||||||
template = self.templates.get_template(basename).source
|
template = self.templates.get_template(basename).source
|
||||||
else:
|
else:
|
||||||
template = self.get_text(name)
|
template = self.get_text(name)
|
||||||
template = inspect.cleandoc(template)
|
template = self.clean_text(template)
|
||||||
templates[name] = template
|
templates[name] = template
|
||||||
return templates
|
return templates
|
||||||
|
|
||||||
|
@ -162,14 +166,22 @@ class ConfigService(abc.ABC):
|
||||||
basename = pathlib.Path(name).name
|
basename = pathlib.Path(name).name
|
||||||
if name in self.custom_templates:
|
if name in self.custom_templates:
|
||||||
text = self.custom_templates[name]
|
text = self.custom_templates[name]
|
||||||
text = inspect.cleandoc(text)
|
text = self.clean_text(text)
|
||||||
self.render_text(name, text, data)
|
rendered = self.render_text(text, data)
|
||||||
elif self.templates.has_template(basename):
|
elif self.templates.has_template(basename):
|
||||||
self.render_template(name, basename, data)
|
rendered = self.render_template(basename, data)
|
||||||
else:
|
else:
|
||||||
text = self.get_text(name)
|
text = self.get_text(name)
|
||||||
text = inspect.cleandoc(text)
|
text = self.clean_text(text)
|
||||||
self.render_text(name, text, data)
|
rendered = self.render_text(text, data)
|
||||||
|
logging.info(
|
||||||
|
"node(%s) service(%s) template(%s): \n%s",
|
||||||
|
self.node.name,
|
||||||
|
self.name,
|
||||||
|
name,
|
||||||
|
rendered,
|
||||||
|
)
|
||||||
|
self.node.nodefile(name, rendered)
|
||||||
|
|
||||||
def run_startup(self) -> None:
|
def run_startup(self) -> None:
|
||||||
for cmd in self.startup:
|
for cmd in self.startup:
|
||||||
|
@ -205,44 +217,30 @@ class ConfigService(abc.ABC):
|
||||||
f"failed to validate"
|
f"failed to validate"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _render(
|
def _render(self, template: Template, data: Dict[str, Any] = None) -> str:
|
||||||
self, name: str, template: Template, data: Dict[str, Any] = None
|
|
||||||
) -> None:
|
|
||||||
if data is None:
|
if data is None:
|
||||||
data = {}
|
data = {}
|
||||||
rendered = template.render_unicode(
|
return template.render_unicode(
|
||||||
node=self.node, config=self.render_config(), **data
|
node=self.node, config=self.render_config(), **data
|
||||||
)
|
)
|
||||||
logging.info(
|
|
||||||
"node(%s) service(%s) template(%s): \n%s",
|
|
||||||
self.node.name,
|
|
||||||
self.name,
|
|
||||||
name,
|
|
||||||
rendered,
|
|
||||||
)
|
|
||||||
self.node.nodefile(name, rendered)
|
|
||||||
|
|
||||||
def render_text(self, name: str, text: str, data: Dict[str, Any] = None) -> None:
|
def render_text(self, text: str, data: Dict[str, Any] = None) -> str:
|
||||||
try:
|
try:
|
||||||
template = Template(text)
|
template = Template(text)
|
||||||
self._render(name, template, data)
|
return self._render(template, data)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise CoreError(
|
raise CoreError(
|
||||||
f"node({self.node.name}) service({self.name}) "
|
f"node({self.node.name}) service({self.name}) "
|
||||||
f"error rendering template({name}): "
|
|
||||||
f"{exceptions.text_error_template().render_unicode()}"
|
f"{exceptions.text_error_template().render_unicode()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def render_template(
|
def render_template(self, basename: str, data: Dict[str, Any] = None) -> str:
|
||||||
self, name: str, basename: str, data: Dict[str, Any] = None
|
|
||||||
) -> None:
|
|
||||||
try:
|
try:
|
||||||
template = self.templates.get_template(basename)
|
template = self.templates.get_template(basename)
|
||||||
self._render(name, template, data)
|
return self._render(template, data)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise CoreError(
|
raise CoreError(
|
||||||
f"node({self.node.name}) service({self.name}) "
|
f"node({self.node.name}) service({self.name}) "
|
||||||
f"error rendering template({name}): "
|
|
||||||
f"{exceptions.text_error_template().render_template()}"
|
f"{exceptions.text_error_template().render_template()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -8,42 +8,6 @@ from core.configservice.base import ConfigService, ConfigServiceMode
|
||||||
GROUP = "ProtoSvc"
|
GROUP = "ProtoSvc"
|
||||||
|
|
||||||
|
|
||||||
class NrlService(ConfigService):
|
|
||||||
name = "NrlBase"
|
|
||||||
group = GROUP
|
|
||||||
directories = []
|
|
||||||
files = []
|
|
||||||
executables = []
|
|
||||||
dependencies = []
|
|
||||||
startup = []
|
|
||||||
validate = []
|
|
||||||
shutdown = []
|
|
||||||
validation_mode = ConfigServiceMode.BLOCKING
|
|
||||||
default_configs = []
|
|
||||||
modes = {}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def generate_config(cls, node, filename):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def firstipv4prefix(node, prefixlen=24):
|
|
||||||
"""
|
|
||||||
Similar to QuaggaService.routerid(). Helper to return the first IPv4
|
|
||||||
prefix of a node, using the supplied prefix length. This ignores the
|
|
||||||
interface's prefix length, so e.g. '/32' can turn into '/24'.
|
|
||||||
"""
|
|
||||||
for ifc in node.netifs():
|
|
||||||
if hasattr(ifc, "control") and ifc.control is True:
|
|
||||||
continue
|
|
||||||
for a in ifc.addrlist:
|
|
||||||
a = a.split("/")[0]
|
|
||||||
if netaddr.valid_ipv4(a):
|
|
||||||
return f"{a}/{prefixlen}"
|
|
||||||
# raise ValueError, "no IPv4 address found"
|
|
||||||
return "0.0.0.0/%s" % prefixlen
|
|
||||||
|
|
||||||
|
|
||||||
class MgenSinkService(ConfigService):
|
class MgenSinkService(ConfigService):
|
||||||
name = "MGEN_Sink"
|
name = "MGEN_Sink"
|
||||||
group = GROUP
|
group = GROUP
|
||||||
|
@ -66,7 +30,7 @@ class MgenSinkService(ConfigService):
|
||||||
return dict(ifnames=ifnames)
|
return dict(ifnames=ifnames)
|
||||||
|
|
||||||
|
|
||||||
class NrlNhdp(NrlService):
|
class NrlNhdp(ConfigService):
|
||||||
name = "NHDP"
|
name = "NHDP"
|
||||||
group = GROUP
|
group = GROUP
|
||||||
directories = []
|
directories = []
|
||||||
|
@ -204,7 +168,7 @@ class OlsrOrg(ConfigService):
|
||||||
return dict(has_smf=has_smf, ifnames=ifnames)
|
return dict(has_smf=has_smf, ifnames=ifnames)
|
||||||
|
|
||||||
|
|
||||||
class MgenActor(NrlService):
|
class MgenActor(ConfigService):
|
||||||
name = "MgenActor"
|
name = "MgenActor"
|
||||||
group = GROUP
|
group = GROUP
|
||||||
directories = []
|
directories = []
|
||||||
|
|
0
daemon/core/configservices/quaggaservices/__init__.py
Normal file
0
daemon/core/configservices/quaggaservices/__init__.py
Normal file
169
daemon/core/configservices/quaggaservices/services.py
Normal file
169
daemon/core/configservices/quaggaservices/services.py
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
import abc
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
import netaddr
|
||||||
|
|
||||||
|
from core import constants
|
||||||
|
from core.configservice.base import ConfigService, ConfigServiceMode
|
||||||
|
from core.nodes.base import CoreNodeBase
|
||||||
|
from core.nodes.interface import CoreInterface
|
||||||
|
|
||||||
|
GROUP = "Quagga"
|
||||||
|
|
||||||
|
|
||||||
|
def has_mtu_mismatch(ifc: CoreInterface) -> bool:
|
||||||
|
"""
|
||||||
|
Helper to detect MTU mismatch and add the appropriate OSPF
|
||||||
|
mtu-ignore command. This is needed when e.g. a node is linked via a
|
||||||
|
GreTap device.
|
||||||
|
"""
|
||||||
|
if ifc.mtu != 1500:
|
||||||
|
return True
|
||||||
|
if not ifc.net:
|
||||||
|
return False
|
||||||
|
for i in ifc.net.netifs():
|
||||||
|
if i.mtu != ifc.mtu:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_router_id(node: CoreNodeBase) -> str:
|
||||||
|
"""
|
||||||
|
Helper to return the first IPv4 address of a node as its router ID.
|
||||||
|
"""
|
||||||
|
for ifc in node.netifs():
|
||||||
|
if hasattr(ifc, "control") and ifc.control is True:
|
||||||
|
continue
|
||||||
|
for a in ifc.addrlist:
|
||||||
|
a = a.split("/")[0]
|
||||||
|
if netaddr.valid_ipv4(a):
|
||||||
|
return a
|
||||||
|
return "0.0.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
class Zebra(ConfigService):
|
||||||
|
name = "zebra"
|
||||||
|
group = GROUP
|
||||||
|
directories = ["/usr/local/etc/quagga", "/var/run/quagga"]
|
||||||
|
files = [
|
||||||
|
"/usr/local/etc/quagga/Quagga.conf",
|
||||||
|
"quaggaboot.sh",
|
||||||
|
"/usr/local/etc/quagga/vtysh.conf",
|
||||||
|
]
|
||||||
|
executables = ["zebra"]
|
||||||
|
dependencies = []
|
||||||
|
startup = ["sh quaggaboot.sh zebra"]
|
||||||
|
validate = ["pidof zebra"]
|
||||||
|
shutdown = ["killall zebra"]
|
||||||
|
validation_mode = ConfigServiceMode.BLOCKING
|
||||||
|
default_configs = []
|
||||||
|
modes = {}
|
||||||
|
|
||||||
|
def data(self) -> Dict[str, Any]:
|
||||||
|
quagga_bin_search = self.node.session.options.get_config(
|
||||||
|
"quagga_bin_search", default="/usr/local/bin /usr/bin /usr/lib/quagga"
|
||||||
|
)
|
||||||
|
quagga_sbin_search = self.node.session.options.get_config(
|
||||||
|
"quagga_sbin_search", default="/usr/local/sbin /usr/sbin /usr/lib/quagga"
|
||||||
|
)
|
||||||
|
quagga_state_dir = constants.QUAGGA_STATE_DIR
|
||||||
|
quagga_conf = self.files[0]
|
||||||
|
|
||||||
|
services = []
|
||||||
|
want_ip4 = False
|
||||||
|
want_ip6 = False
|
||||||
|
for service in self.node.config_services.values():
|
||||||
|
if self.name not in service.dependencies:
|
||||||
|
continue
|
||||||
|
if service.ipv4_routing:
|
||||||
|
want_ip4 = True
|
||||||
|
if service.ipv6_routing:
|
||||||
|
want_ip6 = True
|
||||||
|
services.append(service)
|
||||||
|
|
||||||
|
interfaces = []
|
||||||
|
for ifc in self.node.netifs():
|
||||||
|
ip4s = []
|
||||||
|
ip6s = []
|
||||||
|
for x in ifc.addrlist:
|
||||||
|
addr = x.split("/")[0]
|
||||||
|
if netaddr.valid_ipv4(addr):
|
||||||
|
ip4s.append(x)
|
||||||
|
else:
|
||||||
|
ip6s.append(x)
|
||||||
|
is_control = getattr(ifc, "control", False)
|
||||||
|
interfaces.append((ifc, ip4s, ip6s, is_control))
|
||||||
|
|
||||||
|
return dict(
|
||||||
|
quagga_bin_search=quagga_bin_search,
|
||||||
|
quagga_sbin_search=quagga_sbin_search,
|
||||||
|
quagga_state_dir=quagga_state_dir,
|
||||||
|
quagga_conf=quagga_conf,
|
||||||
|
interfaces=interfaces,
|
||||||
|
want_ip4=want_ip4,
|
||||||
|
want_ip6=want_ip6,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class QuaggaService(abc.ABC):
|
||||||
|
group = GROUP
|
||||||
|
directories = []
|
||||||
|
files = []
|
||||||
|
executables = []
|
||||||
|
dependencies = ["zebra"]
|
||||||
|
startup = []
|
||||||
|
validate = []
|
||||||
|
shutdown = []
|
||||||
|
validation_mode = ConfigServiceMode.BLOCKING
|
||||||
|
default_configs = []
|
||||||
|
modes = {}
|
||||||
|
ipv4_routing = False
|
||||||
|
ipv6_routing = False
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def quagga_interface_config(self, ifc: CoreInterface) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def quagga_config(self) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class Ospfv2(QuaggaService, ConfigService):
|
||||||
|
"""
|
||||||
|
The OSPFv2 service provides IPv4 routing for wired networks. It does
|
||||||
|
not build its own configuration file but has hooks for adding to the
|
||||||
|
unified Quagga.conf file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "OSPFv2"
|
||||||
|
validate = ["pidof ospfd"]
|
||||||
|
shutdown = ["killall ospfd"]
|
||||||
|
ipv4_routing = True
|
||||||
|
|
||||||
|
def quagga_interface_config(self, ifc: CoreInterface) -> str:
|
||||||
|
if has_mtu_mismatch(ifc):
|
||||||
|
return "ip ospf mtu-ignore"
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def quagga_config(self) -> str:
|
||||||
|
router_id = get_router_id(self.node)
|
||||||
|
addresses = []
|
||||||
|
for ifc in self.node.netifs():
|
||||||
|
if getattr(ifc, "control", False):
|
||||||
|
continue
|
||||||
|
for a in ifc.addrlist:
|
||||||
|
addr = a.split("/")[0]
|
||||||
|
if netaddr.valid_ipv4(addr):
|
||||||
|
addresses.append(a)
|
||||||
|
data = dict(router_id=router_id, addresses=addresses)
|
||||||
|
text = """
|
||||||
|
router ospf
|
||||||
|
router-id ${router_id}
|
||||||
|
% for addr in addresses:
|
||||||
|
network ${addr} area 0
|
||||||
|
% endfor
|
||||||
|
!
|
||||||
|
"""
|
||||||
|
return self.render_text(text, data)
|
|
@ -0,0 +1,23 @@
|
||||||
|
% for ifc, ip4s, ip6s in interfaces:
|
||||||
|
interface ${ifc.name}
|
||||||
|
% if want_ip4:
|
||||||
|
% for addr in ip4s:
|
||||||
|
ip address ${addr}
|
||||||
|
% endfor
|
||||||
|
% endif
|
||||||
|
% if want_ip6:
|
||||||
|
% for addr in ip6s:
|
||||||
|
ipv6 address ${addr}
|
||||||
|
% endfor
|
||||||
|
% endif
|
||||||
|
% if is_control:
|
||||||
|
% for service in services:
|
||||||
|
${service.quagga_interface_config(ifc)}
|
||||||
|
% endfor
|
||||||
|
% endif
|
||||||
|
!
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
% for service in services:
|
||||||
|
${service.quagga_config()}
|
||||||
|
% endfor
|
|
@ -0,0 +1,92 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# auto-generated by zebra service (quagga.py)
|
||||||
|
QUAGGA_CONF="${quagga_conf}"
|
||||||
|
QUAGGA_SBIN_SEARCH="${quagga_sbin_search}"
|
||||||
|
QUAGGA_BIN_SEARCH="${quagga_bin_search}"
|
||||||
|
QUAGGA_STATE_DIR="${quagga_state_dir}"
|
||||||
|
|
||||||
|
searchforprog()
|
||||||
|
{
|
||||||
|
prog=$1
|
||||||
|
searchpath=$@
|
||||||
|
ret=
|
||||||
|
for p in $searchpath; do
|
||||||
|
if [ -x $p/$prog ]; then
|
||||||
|
ret=$p
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo $ret
|
||||||
|
}
|
||||||
|
|
||||||
|
confcheck()
|
||||||
|
{
|
||||||
|
CONF_DIR=`dirname $QUAGGA_CONF`
|
||||||
|
# if /etc/quagga exists, point /etc/quagga/Quagga.conf -> CONF_DIR
|
||||||
|
if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/Quagga.conf ]; then
|
||||||
|
ln -s $CONF_DIR/Quagga.conf /etc/quagga/Quagga.conf
|
||||||
|
fi
|
||||||
|
# if /etc/quagga exists, point /etc/quagga/vtysh.conf -> CONF_DIR
|
||||||
|
if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/vtysh.conf ]; then
|
||||||
|
ln -s $CONF_DIR/vtysh.conf /etc/quagga/vtysh.conf
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
bootdaemon()
|
||||||
|
{
|
||||||
|
QUAGGA_SBIN_DIR=$(searchforprog $1 $QUAGGA_SBIN_SEARCH)
|
||||||
|
if [ "z$QUAGGA_SBIN_DIR" = "z" ]; then
|
||||||
|
echo "ERROR: Quagga's '$1' daemon not found in search path:"
|
||||||
|
echo " $QUAGGA_SBIN_SEARCH"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
flags=""
|
||||||
|
|
||||||
|
if [ "$1" = "xpimd" ] && \\
|
||||||
|
grep -E -q '^[[:space:]]*router[[:space:]]+pim6[[:space:]]*$' $QUAGGA_CONF; then
|
||||||
|
flags="$flags -6"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$QUAGGA_SBIN_DIR/$1 $flags -d
|
||||||
|
if [ "$?" != "0" ]; then
|
||||||
|
echo "ERROR: Quagga's '$1' daemon failed to start!:"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
bootquagga()
|
||||||
|
{
|
||||||
|
QUAGGA_BIN_DIR=$(searchforprog 'vtysh' $QUAGGA_BIN_SEARCH)
|
||||||
|
if [ "z$QUAGGA_BIN_DIR" = "z" ]; then
|
||||||
|
echo "ERROR: Quagga's 'vtysh' program not found in search path:"
|
||||||
|
echo " $QUAGGA_BIN_SEARCH"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# fix /var/run/quagga permissions
|
||||||
|
id -u quagga 2>/dev/null >/dev/null
|
||||||
|
if [ "$?" = "0" ]; then
|
||||||
|
chown quagga $QUAGGA_STATE_DIR
|
||||||
|
fi
|
||||||
|
|
||||||
|
bootdaemon "zebra"
|
||||||
|
for r in rip ripng ospf6 ospf bgp babel; do
|
||||||
|
if grep -q "^router \\<$${}{r}\\>" $QUAGGA_CONF; then
|
||||||
|
bootdaemon "$${}{r}d"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if grep -E -q '^[[:space:]]*router[[:space:]]+pim6?[[:space:]]*$' $QUAGGA_CONF; then
|
||||||
|
bootdaemon "xpimd"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$QUAGGA_BIN_DIR/vtysh -b
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$1" != "zebra" ]; then
|
||||||
|
echo "WARNING: '$1': all Quagga daemons are launched by the 'zebra' service!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
confcheck
|
||||||
|
bootquagga
|
|
@ -0,0 +1 @@
|
||||||
|
service integrated-vtysh-config
|
Loading…
Add table
Reference in a new issue