Page MenuHomeVyOS Platform

No OneTemporary

Size
37 KB
Referenced Files
None
Subscribers
None
diff --git a/interface-definitions/vrrp.xml.in b/interface-definitions/vrrp.xml.in
index 2884ef613..89d22f79f 100644
--- a/interface-definitions/vrrp.xml.in
+++ b/interface-definitions/vrrp.xml.in
@@ -1,255 +1,263 @@
<?xml version="1.0"?>
<interfaceDefinition>
<node name="high-availability">
<properties>
<help>High availability settings</help>
</properties>
<children>
<node name="vrrp" owner="${vyos_conf_scripts_dir}/vrrp.py">
<properties>
<priority>800</priority> <!-- after all interfaces and conntrack-sync -->
<help>Virtual Router Redundancy Protocol settings</help>
</properties>
<children>
<tagNode name="group">
<properties>
<help>VRRP group</help>
</properties>
<children>
<leafNode name="interface">
<properties>
<help>Network interface</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
</completionHelp>
</properties>
</leafNode>
<leafNode name="advertise-interval">
<properties>
<help>Advertise interval</help>
<valueHelp>
<format>1-255</format>
<description>Advertise interval in seconds (default: 1)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-255"/>
</constraint>
</properties>
</leafNode>
<node name="authentication">
<properties>
<help>VRRP authentication</help>
</properties>
<children>
<leafNode name="password">
<properties>
<help>VRRP password</help>
<valueHelp>
<format>text</format>
<description>Password string (up to 8 characters)</description>
</valueHelp>
<constraint>
<regex>.{1,8}</regex>
</constraint>
<constraintErrorMessage>Password must not be longer than 8 characters</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="type">
<properties>
<help>Authentication type</help>
<completionHelp>
<list>plaintext-password ah</list>
</completionHelp>
<constraint>
<regex>(plaintext-password|ah)</regex>
</constraint>
<constraintErrorMessage>Authentication type must be plaintext-password or ah</constraintErrorMessage>
</properties>
</leafNode>
</children>
</node>
<leafNode name="description">
<properties>
<help>Group description</help>
</properties>
</leafNode>
<leafNode name="disable">
<properties>
<valueless/>
<help>Disable VRRP group</help>
</properties>
</leafNode>
<node name="health-check">
<properties>
<help>Health check script</help>
</properties>
<children>
<leafNode name="failure-count">
<properties>
<help>Health check failure count required for transition to fault (default: 3)</help>
<constraint>
<validator name="numeric" argument="--positive" />
</constraint>
</properties>
</leafNode>
<leafNode name="interval">
<properties>
<help>Health check execution interval in seconds (default: 60)</help>
<constraint>
<validator name="numeric" argument="--positive"/>
</constraint>
</properties>
</leafNode>
<leafNode name="script">
<properties>
<help>Health check script file</help>
<constraint>
<validator name="script"/>
</constraint>
</properties>
</leafNode>
</children>
</node>
<leafNode name="hello-source-address">
<properties>
<help>VRRP hello source address (IPv4 or IPv6)</help>
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv6-address"/>
</constraint>
<valueHelp>
<format>&lt;IPv4|IPv6&gt;</format>
<description>IPv4 or IPv6 hello source address</description>
</valueHelp>
</properties>
</leafNode>
<leafNode name="peer-address">
<properties>
<help>Unicast VRRP peer address (IPv4 or IPv6)</help>
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv6-address"/>
</constraint>
<valueHelp>
<format>&lt;IPv4|IPv6&gt;</format>
<description>IPv4 or IPv6 unicast peer address</description>
</valueHelp>
</properties>
</leafNode>
<leafNode name="no-preempt">
<properties>
<valueless/>
<help>Disable master preemption</help>
</properties>
</leafNode>
<leafNode name="preempt-delay">
<properties>
<help>Preempt delay (in seconds)</help>
<constraint>
<validator name="numeric" argument="--range 0-1000"/>
</constraint>
</properties>
</leafNode>
<leafNode name="priority">
<properties>
<help>Router priority</help>
<constraint>
<validator name="numeric" argument="--range 1-255"/>
</constraint>
<valueHelp>
<format>1-255</format>
<description>Router priority (default: 100)</description>
</valueHelp>
</properties>
</leafNode>
<leafNode name="rfc3768-compatibility">
<properties>
<valueless/>
<help>Use VRRP virtual MAC address as per RFC3768</help>
</properties>
</leafNode>
<node name="transition-script">
<properties>
<help>VRRP transition scripts</help>
</properties>
<children>
<leafNode name="master">
<properties>
<help>Script to run on VRRP state transition to master</help>
<constraint>
<validator name="script"/>
</constraint>
</properties>
</leafNode>
<leafNode name="backup">
<properties>
<help>Script to run on VRRP state transition to backup</help>
<constraint>
<validator name="script"/>
</constraint>
</properties>
</leafNode>
<leafNode name="fault">
<properties>
<help>Script to run on VRRP state transition to fault</help>
<constraint>
<validator name="script"/>
</constraint>
</properties>
</leafNode>
+ <leafNode name="stop">
+ <properties>
+ <help>Script to run on VRRP state transition to stop</help>
+ <constraint>
+ <validator name="script"/>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
<leafNode name="virtual-address">
<properties>
<multi/>
<help>Virtual address (IPv4 or IPv6, but they must not be mixed in one group)</help>
<constraint>
<validator name="ipv4-host"/>
<validator name="ipv6-host"/>
</constraint>
<constraintErrorMessage>Virtual address must be a valid IPv4 or IPv6 address with prefix length (e.g. 192.0.2.3/24 or 2001:db8:ff::10/64)</constraintErrorMessage>
<valueHelp>
<format>&lt;IPv4|IPv6&gt;</format>
<description>IPv4 or IPv6 virtual address</description>
</valueHelp>
</properties>
</leafNode>
<leafNode name="vrid">
<properties>
<help>Virtual router identifier</help>
<constraint>
<validator name="numeric" argument="--range 1-255"/>
</constraint>
<valueHelp>
<format>1-255</format>
<description>Virtual router identifier</description>
</valueHelp>
</properties>
</leafNode>
</children>
</tagNode>
<tagNode name="sync-group">
<properties>
<help>VRRP sync group</help>
</properties>
<children>
<leafNode name="member">
<properties>
<multi/>
<help>Sync group member</help>
<valueHelp>
<format>text</format>
<description>VRRP group name</description>
</valueHelp>
<completionHelp>
<path>high-availability vrrp group</path>
</completionHelp>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</node>
</children>
</node>
</interfaceDefinition>
diff --git a/python/vyos/keepalived.py b/python/vyos/keepalived.py
index 4114aa736..3984ca792 100644
--- a/python/vyos/keepalived.py
+++ b/python/vyos/keepalived.py
@@ -1,153 +1,140 @@
# Copyright 2018 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/>.
import re
import os
import signal
import json
import vyos.util
pid_file = '/var/run/keepalived.pid'
state_file = '/tmp/keepalived.data'
stats_file = '/tmp/keepalived.stats'
json_file = '/tmp/keepalived.json'
-state_dir = '/var/run/vyos/vrrp/'
-
def vrrp_running():
if not os.path.exists(vyos.keepalived.pid_file) \
or not vyos.util.process_running(vyos.keepalived.pid_file):
return False
else:
return True
def keepalived_running():
return vyos.util.process_running(pid_file)
+## Clear VRRP data after showing
+def remove_vrrp_data(data_file):
+ if data_file == "json" and os.path.exists(json_file):
+ os.remove(json_file)
+ elif data_file == "stats" and os.path.exists(stats_file):
+ os.remove(stats_file)
+ elif data_file == "state" and os.path.exists(state_file):
+ os.remove(state_file)
+
def force_state_data_dump():
pid = vyos.util.read_file(pid_file)
os.kill(int(pid), signal.SIGUSR1)
def force_stats_dump():
pid = vyos.util.read_file(pid_file)
os.kill(int(pid), signal.SIGUSR2)
def force_json_dump():
pid = vyos.util.read_file(pid_file)
os.kill(int(pid), signal.SIGRTMIN+2)
def get_json_data():
with open(json_file, 'r') as f:
j = json.load(f)
return j
def get_statistics():
return vyos.util.read_file(stats_file)
def get_state_data():
return vyos.util.read_file(state_file)
def decode_state(code):
state = None
if code == 0:
state = "INIT"
elif code == 1:
state = "BACKUP"
elif code == 2:
state = "MASTER"
elif code == 3:
state = "FAULT"
else:
state = "UNKNOWN"
return state
-## The functions are mainly for transition script wrappers
-## to compensate for the fact that keepalived doesn't keep persistent
-## state between reloads.
-def get_old_state(group):
- file = os.path.join(state_dir, "{0}.state".format(group))
- if os.path.exists(file):
- with open(file, 'r') as f:
- data = f.read().strip()
- return data
- else:
- return None
-
-def save_state(group, state):
- if not os.path.exists(state_dir):
- os.makedirs(state_dir)
-
- file = os.path.join(state_dir, "{0}.state".format(group))
- with open(file, 'w') as f:
- f.write(state)
-
## These functions are for the old, and hopefully obsolete plaintext
## (non machine-readable) data format introduced by Vyatta back in the days
## They are kept here just in case, if JSON output option turns out or becomes
## insufficient.
def read_state_data():
with open(state_file, 'r') as f:
lines = f.readlines()
return lines
def parse_keepalived_data(data_lines):
vrrp_groups = {}
# Scratch variable
group_name = None
# Sadly there is no explicit end marker in that format, so we have
# only two states, one before the first VRRP instance is encountered
# and one after an instance/"group" was encountered
# We'll set group_name once the first group is encountered,
# and assume we are inside a group if it's set afterwards
#
# It may not be necessary since the keywords found inside groups and before
# the VRRP Topology section seem to have no intersection,
# but better safe than sorry.
for line in data_lines:
if re.match(r'^\s*VRRP Instance', line, re.IGNORECASE):
# Example: "VRRP Instance = Foo"
name = re.match(r'^\s*VRRP Instance\s+=\s+(.*)$', line, re.IGNORECASE).groups()[0].strip()
group_name = name
vrrp_groups[name] = {}
elif re.match(r'^\s*State', line, re.IGNORECASE) and group_name:
# Example: " State = MASTER"
group_state = re.match(r'^\s*State\s+=\s+(.*)$', line, re.IGNORECASE).groups()[0].strip()
vrrp_groups[group_name]["state"] = group_state
elif re.match(r'^\s*Last transition', line, re.IGNORECASE) and group_name:
# Example: " Last transition = 1532043820 (Thu Jul 19 23:43:40 2018)"
trans_time = re.match(r'^\s*Last transition\s+=\s+(\d+)\s', line, re.IGNORECASE).groups()[0]
vrrp_groups[group_name]["last_transition"] = trans_time
elif re.match(r'^\s*Interface', line, re.IGNORECASE) and group_name:
# Example: " Interface = eth0.30"
interface = re.match(r'\s*Interface\s+=\s+(.*)$', line, re.IGNORECASE).groups()[0].strip()
vrrp_groups[group_name]["interface"] = interface
elif re.match(r'^\s*Virtual Router ID', line, re.IGNORECASE) and group_name:
# Example: " Virtual Router ID = 14"
vrid = re.match(r'^\s*Virtual Router ID\s+=\s+(.*)$', line, re.IGNORECASE).groups()[0].strip()
vrrp_groups[group_name]["vrid"] = vrid
elif re.match(r'^\s*------< Interfaces', line, re.IGNORECASE):
# Interfaces section appears to always be present,
# and there's nothing of interest for us below that section,
# so we use it as an end of input marker
break
return vrrp_groups
diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py
index d31be4cfb..1d8477769 100755
--- a/src/conf_mode/vrrp.py
+++ b/src/conf_mode/vrrp.py
@@ -1,345 +1,351 @@
#!/usr/bin/env python3
#
# Copyright (C) 2018 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os
import sys
import subprocess
import ipaddress
import jinja2
import vyos.config
import vyos.keepalived
from vyos import ConfigError
config_file = "/etc/keepalived/keepalived.conf"
config_tmpl = """
# Autogenerated by VyOS
# Do not edit this file, all your changes will be lost
# on next commit or reboot
global_defs {
dynamic_interfaces
+ script_user root
}
{% for group in groups -%}
{% if group.health_check_script -%}
vrrp_script healthcheck_{{ group.name }} {
script "{{ group.health_check_script }}"
interval {{ group.health_check_interval }}
fall {{ group.health_check_count }}
rise 1
}
{% endif %}
vrrp_instance {{ group.name }} {
{% if group.description -%}
# {{ group.description }}
{% endif -%}
state BACKUP
interface {{ group.interface }}
virtual_router_id {{ group.vrid }}
priority {{ group.priority }}
advert_int {{ group.advertise_interval }}
{% if group.preempt -%}
preempt_delay {{ group.preempt_delay }}
{% else -%}
nopreempt
{% endif -%}
{% if group.peer_address -%}
unicast_peer { {{ group.peer_address }} }
{% endif -%}
{% if group.hello_source -%}
{%- if group.peer_address -%}
unicast_src_ip {{ group.hello_source }}
{%- else -%}
mcast_src_ip {{ group.hello_source }}
{%- endif %}
{% endif -%}
{% if group.use_vmac and group.peer_address -%}
use_vmac {{group.interface}}v{{group.vrid}}
vmac_xmit_base
{% elif group.use_vmac -%}
use_vmac {{group.interface}}v{{group.vrid}}
{% endif -%}
{% if group.auth_password -%}
authentication {
auth_pass "{{ group.auth_password }}"
auth_type {{ group.auth_type }}
}
{% endif -%}
virtual_ipaddress {
{% for addr in group.virtual_addresses -%}
{{ addr }}
{% endfor -%}
}
{% if group.health_check_script -%}
track_script {
healthcheck_{{ group.name }}
}
{% endif -%}
{% if group.master_script -%}
notify_master "/usr/libexec/vyos/system/vrrp-script-wrapper.py --state master --group {{ group.name }} --interface {{ group.interface }} {{ group.master_script }}"
{% endif -%}
{% if group.backup_script -%}
notify_backup "/usr/libexec/vyos/system/vrrp-script-wrapper.py --state backup --group {{ group.name }} --interface {{ group.interface }} {{ group.backup_script }}"
{% endif -%}
{% if group.fault_script -%}
notify_fault "/usr/libexec/vyos/system/vrrp-script-wrapper.py --state fault --group {{ group.name }} --interface {{ group.interface }} {{ group.fault_script }}"
{% endif -%}
+
+ {% if group.stop_script -%}
+ notify_stop "/usr/libexec/vyos/system/vrrp-script-wrapper.py --state stop --group {{ group.name }} --interface {{ group.interface }} {{ group.stop_script }}"
+ {% endif -%}
}
{% endfor -%}
{% for sync_group in sync_groups -%}
vrrp_sync_group {{ sync_group.name }} {
group {
{% for member in sync_group.members -%}
{{ member }}
{% endfor -%}
}
{% if sync_group.conntrack_sync -%}
notify_master "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh master {{ sync_group.name }}"
notify_backup "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh backup {{ sync_group.name }}"
notify_fault "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh fault {{ sync_group.name }}"
{% endif -%}
}
{% endfor -%}
"""
def get_config():
vrrp_groups = []
sync_groups = []
config = vyos.config.Config()
# Get the VRRP groups
for group_name in config.list_nodes("high-availability vrrp group"):
config.set_level("high-availability vrrp group {0}".format(group_name))
# Retrieve the values
group = {"preempt": True, "use_vmac": False, "disable": False}
if config.exists("disable"):
group["disable"] = True
group["name"] = group_name
group["vrid"] = config.return_value("vrid")
group["interface"] = config.return_value("interface")
group["description"] = config.return_value("description")
group["advertise_interval"] = config.return_value("advertise-interval")
group["priority"] = config.return_value("priority")
group["hello_source"] = config.return_value("hello-source-address")
group["peer_address"] = config.return_value("peer-address")
group["sync_group"] = config.return_value("sync-group")
group["preempt_delay"] = config.return_value("preempt-delay")
group["virtual_addresses"] = config.return_values("virtual-address")
group["auth_password"] = config.return_value("authentication password")
group["auth_type"] = config.return_value("authentication type")
group["health_check_script"] = config.return_value("health-check script")
group["health_check_interval"] = config.return_value("health-check interval")
group["health_check_count"] = config.return_value("health-check failure-count")
group["master_script"] = config.return_value("transition-script master")
group["backup_script"] = config.return_value("transition-script backup")
group["fault_script"] = config.return_value("transition-script fault")
+ group["stop_script"] = config.return_value("transition-script stop")
if config.exists("no-preempt"):
group["preempt"] = False
if config.exists("rfc3768-compatibility"):
group["use_vmac"] = True
# Substitute defaults where applicable
if not group["advertise_interval"]:
group["advertise_interval"] = 1
if not group["priority"]:
group["priority"] = 100
if not group["preempt_delay"]:
group["preempt_delay"] = 0
if not group["health_check_interval"]:
group["health_check_interval"] = 60
if not group["health_check_count"]:
group["health_check_count"] = 3
# FIXUP: translate our option for auth type to keepalived's syntax
# for simplicity
if group["auth_type"]:
if group["auth_type"] == "plaintext-password":
group["auth_type"] = "PASS"
else:
group["auth_type"] = "AH"
vrrp_groups.append(group)
config.set_level("")
# Get the sync group used for conntrack-sync
conntrack_sync_group = None
if config.exists("service conntrack-sync failover-mechanism vrrp"):
conntrack_sync_group = config.return_value("service conntrack-sync failover-mechanism vrrp sync-group")
# Get the sync groups
for sync_group_name in config.list_nodes("high-availability vrrp sync-group"):
config.set_level("high-availability vrrp sync-group {0}".format(sync_group_name))
sync_group = {"conntrack_sync": False}
sync_group["name"] = sync_group_name
sync_group["members"] = config.return_values("member")
if conntrack_sync_group:
if conntrack_sync_group == sync_group_name:
sync_group["conntrack_sync"] = True
sync_groups.append(sync_group)
return (vrrp_groups, sync_groups)
def verify(data):
vrrp_groups, sync_groups = data
for group in vrrp_groups:
# Check required fields
if not group["vrid"]:
raise ConfigError("vrid is required but not set in VRRP group {0}".format(group["name"]))
if not group["interface"]:
raise ConfigError("interface is required but not set in VRRP group {0}".format(group["name"]))
if not group["virtual_addresses"]:
raise ConfigError("virtual-address is required but not set in VRRP group {0}".format(group["name"]))
if group["auth_password"] and (not group["auth_type"]):
raise ConfigError("authentication type is required but not set in VRRP group {0}".format(group["name"]))
# Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction
# XXX: filter on map object is destructive, so we force it to list.
# Additionally, filter objects always evaluate to True, empty or not,
# so we force them to lists as well.
vaddrs = list(map(lambda i: ipaddress.ip_interface(i), group["virtual_addresses"]))
vaddrs4 = list(filter(lambda x: isinstance(x, ipaddress.IPv4Interface), vaddrs))
vaddrs6 = list(filter(lambda x: isinstance(x, ipaddress.IPv6Interface), vaddrs))
if vaddrs4 and vaddrs6:
raise ConfigError("VRRP group {0} mixes IPv4 and IPv6 virtual addresses, this is not allowed. Create separate groups for IPv4 and IPv6".format(group["name"]))
if vaddrs4:
if group["hello_source"]:
hsa = ipaddress.ip_address(group["hello_source"])
if isinstance(hsa, ipaddress.IPv6Address):
raise ConfigError("VRRP group {0} uses IPv4 but its hello-source-address is IPv6".format(group["name"]))
if group["peer_address"]:
pa = ipaddress.ip_address(group["peer_address"])
if isinstance(pa, ipaddress.IPv6Address):
raise ConfigError("VRRP group {0} uses IPv4 but its peer-address is IPv6".format(group["name"]))
if vaddrs6:
if group["hello_source"]:
hsa = ipaddress.ip_address(group["hello_source"])
if isinstance(hsa, ipaddress.IPv4Address):
raise ConfigError("VRRP group {0} uses IPv6 but its hello-source-address is IPv4".format(group["name"]))
if group["peer_address"]:
pa = ipaddress.ip_address(group["peer_address"])
if isinstance(pa, ipaddress.IPv4Address):
raise ConfigError("VRRP group {0} uses IPv6 but its peer-address is IPv4".format(group["name"]))
# Disallow same VRID on multiple interfaces
_groups = sorted(vrrp_groups, key=(lambda x: x["interface"]))
count = len(_groups) - 1
index = 0
while (index < count):
if (_groups[index]["vrid"] == _groups[index + 1]["vrid"]) and (_groups[index]["interface"] == _groups[index + 1]["interface"]):
raise ConfigError("VRID {0} is used in groups {1} and {2} that both use interface {3}. Groups on the same interface must use different VRIDs".format(
_groups[index]["vrid"], _groups[index]["name"], _groups[index + 1]["name"], _groups[index]["interface"]))
else:
index += 1
# Check sync groups
vrrp_group_names = list(map(lambda x: x["name"], vrrp_groups))
for sync_group in sync_groups:
for m in sync_group["members"]:
if not (m in vrrp_group_names):
raise ConfigError("VRRP sync-group {0} refers to VRRP group {1}, but group {1} does not exist".format(sync_group["name"], m))
def generate(data):
vrrp_groups, sync_groups = data
# Remove disabled groups from the sync group member lists
for sync_group in sync_groups:
for member in sync_group["members"]:
g = list(filter(lambda x: x["name"] == member, vrrp_groups))[0]
if g["disable"]:
print("Warning: ignoring disabled VRRP group {0} in sync-group {1}".format(g["name"], sync_group["name"]))
# Filter out disabled groups
vrrp_groups = list(filter(lambda x: x["disable"] != True, vrrp_groups))
tmpl = jinja2.Template(config_tmpl)
config_text = tmpl.render({"groups": vrrp_groups, "sync_groups": sync_groups})
with open(config_file, 'w') as f:
f.write(config_text)
return None
def apply(data):
vrrp_groups, sync_groups = data
if vrrp_groups:
if not vyos.keepalived.vrrp_running():
print("Starting the VRRP process")
ret = subprocess.call("sudo systemctl restart keepalived.service", shell=True)
else:
print("Reloading the VRRP process")
ret = subprocess.call("sudo systemctl reload keepalived.service", shell=True)
if ret != 0:
raise ConfigError("keepalived failed to start")
else:
# VRRP is removed in the commit
print("Stopping the VRRP process")
subprocess.call("sudo systemctl stop keepalived.service", shell=True)
os.unlink(config_file)
return None
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print("VRRP error: {0}".format(str(e)))
sys.exit(1)
diff --git a/src/op_mode/vrrp.py b/src/op_mode/vrrp.py
index 54e1bfb57..8d1369823 100755
--- a/src/op_mode/vrrp.py
+++ b/src/op_mode/vrrp.py
@@ -1,101 +1,104 @@
#!/usr/bin/env python3
#
# Copyright (C) 2018 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import sys
import time
import argparse
import tabulate
import vyos.keepalived
import vyos.util
def print_summary():
try:
vyos.keepalived.force_json_dump()
# Wait for keepalived to produce the data
# Replace with inotify or similar if it proves problematic
time.sleep(0.2)
json_data = vyos.keepalived.get_json_data()
+ vyos.keepalived.remove_vrrp_data("json")
except:
print("VRRP information is not available")
sys.exit(1)
groups = []
for group in json_data:
data = group["data"]
name = data["iname"]
ltrans_timestamp = float(data["last_transition"])
ltrans_time = vyos.util.seconds_to_human(int(time.time() - ltrans_timestamp))
interface = data["ifp_ifname"]
vrid = data["vrid"]
state = vyos.keepalived.decode_state(data["state"])
row = [name, interface, vrid, state, ltrans_time]
groups.append(row)
headers = ["Name", "Interface", "VRID", "State", "Last Transition"]
output = tabulate.tabulate(groups, headers)
print(output)
def print_statistics():
try:
vyos.keepalived.force_stats_dump()
time.sleep(0.2)
output = vyos.keepalived.get_statistics()
print(output)
+ vyos.keepalived.remove_vrrp_data("stats")
except:
print("VRRP statistics are not available")
sys.exit(1)
def print_state_data():
try:
vyos.keepalived.force_state_data_dump()
time.sleep(0.2)
output = vyos.keepalived.get_state_data()
print(output)
+ vyos.keepalived.remove_vrrp_data("state")
except:
print("VRRP information is not available")
sys.exit(1)
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-s", "--summary", action="store_true", help="Print VRRP summary")
group.add_argument("-t", "--statistics", action="store_true", help="Print VRRP statistics")
group.add_argument("-d", "--data", action="store_true", help="Print detailed VRRP data")
args = parser.parse_args()
# Exit early if VRRP is dead or not configured
if not vyos.keepalived.vrrp_running():
print("VRRP is not running")
sys.exit(0)
if args.summary:
print_summary()
elif args.statistics:
print_statistics()
elif args.data:
print_state_data()
else:
parser.print_help()
sys.exit(1)
diff --git a/src/system/vrrp-script-wrapper.py b/src/system/vrrp-script-wrapper.py
index ccd640128..c28ecba55 100755
--- a/src/system/vrrp-script-wrapper.py
+++ b/src/system/vrrp-script-wrapper.py
@@ -1,81 +1,64 @@
#!/usr/bin/env python3
#
# Copyright (C) 2018 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
import os
import sys
import subprocess
import argparse
import syslog
import vyos.util
-import vyos.keepalived
parser = argparse.ArgumentParser()
parser.add_argument("-t", "--state", type=str, help="VRRP state")
parser.add_argument("-g", "--group", type=str, help="VRRP group")
parser.add_argument("-i", "--interface", type=str, help="Network interface")
parser.add_argument("script", nargs='+')
syslog.openlog('vyos-vrrp-wrapper')
args = parser.parse_args()
if not args.script or not args.state or not args.group \
or not args.interface:
parser.print_usage()
sys.exit(1)
# Fixup: the reason we take multiple "script" arguments is that people may want
# to pass arguments to the script
args.script = " ".join(args.script)
-# Get the old state if it exists and compare it to the current state received
-# in command line options to avoid executing scripts if no real transition occured.
-# This is necessary because keepalived does not keep persistent state data even between
-# config reloads and will cheerfully execute everything whether it's required or not.
-
-old_state = vyos.keepalived.get_old_state(args.group)
-
-if (old_state is None) or (old_state != args.state):
- exitcode = 0
-
- # Run the script and save the new state
-
- # Change the process GID to the config owners group to avoid screwing up
- # running config permissions
- os.setgid(vyos.util.get_cfg_group_id())
-
- syslog.syslog(syslog.LOG_NOTICE, 'Running transition script {0} for VRRP group {1}'.format(args.script, args.group))
- try:
- ret = subprocess.call("%s %s %s %s" % ( args.script, args.state, args.interface, args.group), shell=True)
- if ret != 0:
- syslog.syslog(syslog.LOG_ERR, "Transition script {0} failed, exit status: {1}".format(args.script, ret))
- exitcode = ret
- except Exception as e:
- syslog.syslog(syslog.LOG_ERR, "Failed to execute transition script {0}: {1}".format(args.script, e))
- exitcode = 1
-
- if exitcode == 0:
- syslog.syslog(syslog.LOG_NOTICE, "Transition script {0} executed successfully".format(args.script))
-
- vyos.keepalived.save_state(args.group, args.state)
-else:
- syslog.syslog(syslog.LOG_NOTICE, "State of the group {0} has not changed, not running transition script".format(args.group))
+exitcode = 0
+# Change the process GID to the config owners group to avoid screwing up
+# running config permissions
+os.setgid(vyos.util.get_cfg_group_id())
+syslog.syslog(syslog.LOG_NOTICE, 'Running transition script {0} for VRRP group {1}'.format(args.script, args.group))
+try:
+ ret = subprocess.call("%s %s %s %s" % ( args.script, args.state, args.interface, args.group), shell=True)
+ if ret != 0:
+ syslog.syslog(syslog.LOG_ERR, "Transition script {0} failed, exit status: {1}".format(args.script, ret))
+ exitcode = ret
+except Exception as e:
+ syslog.syslog(syslog.LOG_ERR, "Failed to execute transition script {0}: {1}".format(args.script, e))
+ exitcode = 1
+
+if exitcode == 0:
+ syslog.syslog(syslog.LOG_NOTICE, "Transition script {0} executed successfully".format(args.script))
syslog.closelog()
sys.exit(exitcode)

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 6, 6:56 AM (7 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4652916
Default Alt Text
(37 KB)

Event Timeline