summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--python/vyos/ifconfig/vrrp.py145
-rw-r--r--python/vyos/keepalived.py140
-rwxr-xr-xsrc/conf_mode/vrrp.py21
-rwxr-xr-xsrc/op_mode/vrrp.py87
4 files changed, 161 insertions, 232 deletions
diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py
new file mode 100644
index 000000000..29b10dd9e
--- /dev/null
+++ b/python/vyos/ifconfig/vrrp.py
@@ -0,0 +1,145 @@
+# Copyright 2019 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 os
+import json
+import signal
+from time import time
+from time import sleep
+
+from tabulate import tabulate
+
+from vyos import airbag
+from vyos import util
+
+
+class VRRPError(Exception):
+ pass
+
+class VRRP(object):
+ _vrrp_prefix = '00:00:5E:00:01:'
+ location = {
+ 'pid': '/run/keepalived.pid',
+ 'fifo': '/run/keepalived_notify_fifo',
+ 'state': '/tmp/keepalived.data',
+ 'stats': '/tmp/keepalived.stats',
+ 'json': '/tmp/keepalived.json',
+ 'daemon': '/etc/default/keepalived',
+ 'config': '/etc/keepalived/keepalived.conf',
+ 'vyos': '/run/keepalived_config.dict',
+ }
+
+ _signal = {
+ 'state': signal.SIGUSR1,
+ 'stats': signal.SIGUSR2,
+ 'json': signal.SIGRTMIN + 2,
+ }
+
+ _name = {
+ 'state': 'information',
+ 'stats': 'statistics',
+ 'json': 'data',
+ }
+
+ state = {
+ 0: 'INIT',
+ 1: 'BACKUP',
+ 2: 'MASTER',
+ 3: 'FAULT',
+ # UNKNOWN
+ }
+
+ def __init__(self,ifname):
+ self.ifname = ifname
+
+ def enabled(self):
+ return self.ifname in self.active_interfaces()
+
+ @classmethod
+ def active_interfaces(cls):
+ if not os.path.exists(cls.location['pid']):
+ return []
+ data = cls.collect('json')
+ return [group['data']['ifp_ifname'] for group in json.loads(data)]
+
+ @classmethod
+ def decode_state(cls, code):
+ return cls.state.get(code,'UNKNOWN')
+
+ # used in conf mode
+ @classmethod
+ def is_running(cls):
+ if not os.path.exists(cls.location['pid']):
+ return False
+ return util.process_running(cls.location['pid'])
+
+ @classmethod
+ def collect(cls, what):
+ fname = cls.location[what]
+ try:
+ # send signal to generate the configuration file
+ pid = util.read_file(cls.location['pid'])
+ os.kill(int(pid), cls._signal[what])
+
+ # shoud look for file size change ?
+ sleep(0.2)
+ return util.read_file(fname)
+ except Exception:
+ name = cls._name[what]
+ raise VRRPError(f'VRRP {name} is not available')
+ finally:
+ if os.path.exists(fname):
+ os.remove(fname)
+
+ @classmethod
+ def disabled(cls):
+ if not os.path.exists(cls.location['vyos']):
+ return []
+
+ disabled = []
+ config = json.loads(util.readfile(cls.location['vyos']))
+
+ # add disabled groups to the list
+ for group in config['vrrp_groups']:
+ if group['disable']:
+ disabled.append(
+ [group['name'], group['interface'], group['vrid'], 'DISABLED', ''])
+
+ # return list with disabled instances
+ return disabled
+
+ @classmethod
+ def format (cls, data):
+ headers = ["Name", "Interface", "VRID", "State", "Last Transition"]
+ groups = []
+
+ data = json.loads(data)
+ for group in data:
+ data = group['data']
+
+ name = data['iname']
+ intf = data['ifp_ifname']
+ vrid = data['vrid']
+ state = cls.decode_state(data["state"])
+
+ since = int(time() - float(data['last_transition']))
+ last = util.seconds_to_human(since)
+
+ groups.append([name, intf, vrid, state, last])
+
+ # add to the active list disabled instances
+ groups.extend(cls.disabled())
+ return(tabulate(groups, headers))
+
diff --git a/python/vyos/keepalived.py b/python/vyos/keepalived.py
deleted file mode 100644
index 3984ca792..000000000
--- a/python/vyos/keepalived.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# 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'
-
-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
-
-## 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 b9b0405e2..f358891a5 100755
--- a/src/conf_mode/vrrp.py
+++ b/src/conf_mode/vrrp.py
@@ -22,16 +22,13 @@ from json import dumps
from pathlib import Path
import vyos.config
-import vyos.keepalived
from vyos import ConfigError
from vyos.util import call
from vyos.template import render
+from vyos.ifconfig.vrrp import VRRP
-daemon_file = "/etc/default/keepalived"
-config_file = "/etc/keepalived/keepalived.conf"
-config_dict_path = "/run/keepalived_config.dict"
def get_config():
vrrp_groups = []
@@ -127,7 +124,7 @@ def get_config():
sync_groups.append(sync_group)
# create a file with dict with proposed configuration
- with open("{}.temp".format(config_dict_path), 'w') as dict_file:
+ with open("{}.temp".format(VRRP.location['vyos']), 'w') as dict_file:
dict_file.write(dumps({'vrrp_groups': vrrp_groups, 'sync_groups': sync_groups}))
return (vrrp_groups, sync_groups)
@@ -212,9 +209,9 @@ def generate(data):
# Filter out disabled groups
vrrp_groups = list(filter(lambda x: x["disable"] is not True, vrrp_groups))
- render(config_file, 'vrrp/keepalived.conf.tmpl',
- {"groups": vrrp_groups, "sync_groups": sync_groups})
- render(daemon_file, 'vrrp/daemon.tmpl', {})
+ render(VRRP.location['config'], 'vrrp/keepalived.conf.tmpl',
+ {"groups": vrrp_groups, "sync_groups": sync_groups})
+ render(VRRP.location['daemon'], 'vrrp/daemon.tmpl', {})
return None
@@ -223,12 +220,12 @@ def apply(data):
if vrrp_groups:
# safely rename a temporary file with configuration dict
try:
- dict_file = Path("{}.temp".format(config_dict_path))
- dict_file.rename(Path(config_dict_path))
+ dict_file = Path("{}.temp".format(VRRP.location['vyos']))
+ dict_file.rename(Path(VRRP.location['vyos']))
except Exception as err:
print("Unable to rename the file with keepalived config for FIFO pipe: {}".format(err))
- if not vyos.keepalived.vrrp_running():
+ if not VRRP.is_running():
print("Starting the VRRP process")
ret = call("sudo systemctl restart keepalived.service")
else:
@@ -241,7 +238,7 @@ def apply(data):
# VRRP is removed in the commit
print("Stopping the VRRP process")
call("sudo systemctl stop keepalived.service")
- os.unlink(config_file)
+ os.unlink(VRRP.location['daemon'])
return None
diff --git a/src/op_mode/vrrp.py b/src/op_mode/vrrp.py
index 8a993f92c..923cfa4d4 100755
--- a/src/op_mode/vrrp.py
+++ b/src/op_mode/vrrp.py
@@ -24,81 +24,8 @@ import tabulate
import vyos.keepalived
import vyos.util
-config_dict_path = '/run/keepalived_config.dict'
-
-
-# get disabled instances from a config
-def vrrp_get_disabled():
- # read the dictionary file with configuration
- with open(config_dict_path, 'r') as dict_file:
- vrrp_config_dict = json.load(dict_file)
- vrrp_disabled = []
- # add disabled groups to the list
- for vrrp_group in vrrp_config_dict['vrrp_groups']:
- if vrrp_group['disable']:
- vrrp_disabled.append([vrrp_group['name'], vrrp_group['interface'], vrrp_group['vrid'], 'DISABLED', ''])
- # return list with disabled instances
- return vrrp_disabled
-
-
-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)
-
- # add to the active list disabled instances
- groups.extend(vrrp_get_disabled())
- 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)
+from vyos.ifconfig.vrrp import VRRP
+from vyos.ifconfig.vrrp import VRRPError
parser = argparse.ArgumentParser()
@@ -110,16 +37,16 @@ group.add_argument("-d", "--data", action="store_true", help="Print detailed VRR
args = parser.parse_args()
# Exit early if VRRP is dead or not configured
-if not vyos.keepalived.vrrp_running():
- print("VRRP is not running")
+if not VRRP.is_running():
+ print('VRRP is not running')
sys.exit(0)
if args.summary:
- print_summary()
+ print(VRRP.format(VRRP.collect('json')))
elif args.statistics:
- print_statistics()
+ print(VRRP.collect('stats'))
elif args.data:
- print_state_data()
+ print(VRRP.collect('state'))
else:
parser.print_help()
sys.exit(1)