summaryrefslogtreecommitdiff
path: root/src/conf_mode/protocols_failover.py
diff options
context:
space:
mode:
authorDaniil Baturin <daniil@vyos.io>2025-10-08 17:04:19 +0100
committerGitHub <noreply@github.com>2025-10-08 17:04:19 +0100
commit1c181499a3af4b6c170d3fea2b96af58bb0a34a7 (patch)
tree44498595ab266f4e569b50db786fc42e90a6b72b /src/conf_mode/protocols_failover.py
parentfc178aaf00f7a9dc4589567ae3e17cb9718d9ad3 (diff)
parent931e5b5712b4fa8b13027291eb21881c40b8a051 (diff)
downloadvyos-1x-1c181499a3af4b6c170d3fea2b96af58bb0a34a7.tar.gz
vyos-1x-1c181499a3af4b6c170d3fea2b96af58bb0a34a7.zip
Merge pull request #4749 from hedrok/T7803-failover-vrf
T7803: Make failover route vrf-aware
Diffstat (limited to 'src/conf_mode/protocols_failover.py')
-rwxr-xr-xsrc/conf_mode/protocols_failover.py83
1 files changed, 72 insertions, 11 deletions
diff --git a/src/conf_mode/protocols_failover.py b/src/conf_mode/protocols_failover.py
index 9b50cc1ed..956c060f4 100755
--- a/src/conf_mode/protocols_failover.py
+++ b/src/conf_mode/protocols_failover.py
@@ -15,12 +15,15 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
+import os
from pathlib import Path
+from sys import argv
from vyos.config import Config
from vyos.template import render
from vyos.utils.process import call
+from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
from vyos import airbag
@@ -28,9 +31,24 @@ airbag.enable()
service_name = 'vyos-failover'
-service_conf = Path(f'/run/{service_name}.conf')
+service_conf_dir = Path(f'/run/{service_name}.conf.d/')
systemd_service = '/run/systemd/system/vyos-failover.service'
-rt_proto_failover = '/etc/iproute2/rt_protos.d/failover.conf'
+rt_proto_failover = Path('/etc/iproute2/rt_protos.d/failover.conf')
+
+
+def get_vrf_name():
+ if argv and len(argv) > 1:
+ return argv[1]
+ return None
+
+
+def get_service_conf_path():
+ vrf_name = get_vrf_name()
+ if vrf_name:
+ filename = f'vrf-{vrf_name}.conf'
+ else:
+ filename = 'default.conf'
+ return service_conf_dir / filename
def get_config(config=None):
@@ -39,7 +57,14 @@ def get_config(config=None):
else:
conf = Config()
- base = ['protocols', 'failover']
+ vrf_name = get_vrf_name()
+ if vrf_name:
+ base = ['vrf', 'name', vrf_name]
+ else:
+ base = []
+
+ base += ['protocols', 'failover']
+
failover = conf.get_config_dict(base, key_mangling=('-', '_'),
get_first_key=True)
@@ -47,6 +72,9 @@ def get_config(config=None):
if failover.get('route') is not None:
failover = conf.merge_defaults(failover, recursive=True)
+ if failover:
+ failover['vrf_context'] = vrf_name
+
return failover
def verify(failover):
@@ -75,33 +103,66 @@ def verify(failover):
if check_type == 'tcp' and 'port' not in next_hop_config['check']:
raise ConfigError(f'Check port for next-hop "{next_hop}" and type TCP is mandatory!')
+ errors = {
+ 'icmp': {},
+ 'tcp': {
+ 'interface': 'Check target "interface" option does nothing for type TCP. Use "vrf" if needed',
+ },
+ 'arp': {
+ 'vrf': 'Check target "vrf" option is incompatible with type ARP, use "interface" option if needed',
+ },
+ }
+
+ for target, target_config in next_hop_config['check']['target'].items():
+ for key, msg in errors[check_type].items():
+ if key in target_config:
+ raise ConfigError(msg)
+
return None
+
def generate(failover):
+ service_conf = get_service_conf_path()
if not failover:
service_conf.unlink(missing_ok=True)
+ try:
+ os.rmdir(service_conf_dir)
+ # Ignore if directory doesn't exist
+ # or not empty (probably configs for other VRFs are there)
+ except (FileNotFoundError, OSError):
+ pass
return None
# Add own rt_proto 'failover'
# Helps to detect all own routes 'proto failover'
- with open(rt_proto_failover, 'w') as f:
- f.write('111 failover\n')
+ rt_proto_failover.write_text('111 failover\n')
+
+ service_conf_dir.mkdir(exist_ok=True)
# Write configuration file
conf_json = json.dumps(failover, indent=4)
service_conf.write_text(conf_json)
- render(systemd_service, 'protocols/systemd_vyos_failover_service.j2', failover)
+ render(
+ systemd_service,
+ 'protocols/systemd_vyos_failover_service.j2',
+ {'config_dir': str(service_conf_dir)},
+ )
return None
def apply(failover):
- if not failover:
+ # If directory is removed - we can stop the service
+ if not service_conf_dir.is_dir():
call(f'systemctl stop {service_name}.service')
- call('ip route flush protocol failover')
- else:
call('systemctl daemon-reload')
- call(f'systemctl restart {service_name}.service')
- call(f'ip route flush protocol failover')
+ # Otherwise even if `failover` is False, service is
+ # still needed for other VRFs.
+ else:
+ # Daemon watches for configuration updates, so we need only
+ # to start it if it is not started yet
+ if not is_systemd_service_running(service_name):
+ call('systemctl daemon-reload')
+ call(f'systemctl start {service_name}.service')
return None