Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F118306462
keepalived-fifo.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
8 KB
Referenced Files
None
Subscribers
None
keepalived-fifo.py
View Options
#!/usr/bin/env python3
#
# Copyright (C) 2020 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
time
import
signal
import
argparse
import
threading
import
re
import
json
from
pathlib
import
Path
from
queue
import
Queue
import
logging
from
logging.handlers
import
SysLogHandler
from
vyos.util
import
cmd
# configure logging
logger
=
logging
.
getLogger
(
__name__
)
logs_format
=
logging
.
Formatter
(
'
%(filename)s
:
%(message)s
'
)
logs_handler_syslog
=
SysLogHandler
(
'/dev/log'
)
logs_handler_syslog
.
setFormatter
(
logs_format
)
logger
.
addHandler
(
logs_handler_syslog
)
logger
.
setLevel
(
logging
.
DEBUG
)
# class for all operations
class
KeepalivedFifo
:
# init - read command arguments
def
__init__
(
self
):
logger
.
info
(
"Starting FIFO pipe for Keepalived"
)
# define program arguments
cmd_args_parser
=
argparse
.
ArgumentParser
(
description
=
'Create FIFO pipe for keepalived and process notify events'
,
add_help
=
False
)
cmd_args_parser
.
add_argument
(
'PIPE'
,
help
=
'path to the FIFO pipe'
)
# parse arguments
cmd_args
=
cmd_args_parser
.
parse_args
()
self
.
_config_load
()
self
.
pipe_path
=
cmd_args
.
PIPE
# create queue for messages and events for syncronization
self
.
message_queue
=
Queue
(
maxsize
=
100
)
self
.
stopme
=
threading
.
Event
()
self
.
message_event
=
threading
.
Event
()
# load configuration
def
_config_load
(
self
):
try
:
# read the dictionary file with configuration
with
open
(
'/run/keepalived_config.dict'
,
'r'
)
as
dict_file
:
vrrp_config_dict
=
json
.
load
(
dict_file
)
self
.
vrrp_config
=
{
'vrrp_groups'
:
{},
'sync_groups'
:
{}}
# save VRRP instances to the new dictionary
for
vrrp_group
in
vrrp_config_dict
[
'vrrp_groups'
]:
self
.
vrrp_config
[
'vrrp_groups'
][
vrrp_group
[
'name'
]]
=
{
'STOP'
:
vrrp_group
.
get
(
'stop_script'
),
'FAULT'
:
vrrp_group
.
get
(
'fault_script'
),
'BACKUP'
:
vrrp_group
.
get
(
'backup_script'
),
'MASTER'
:
vrrp_group
.
get
(
'master_script'
)
}
# save VRRP sync groups to the new dictionary
for
sync_group
in
vrrp_config_dict
[
'sync_groups'
]:
self
.
vrrp_config
[
'sync_groups'
][
sync_group
[
'name'
]]
=
{
'STOP'
:
sync_group
.
get
(
'stop_script'
),
'FAULT'
:
sync_group
.
get
(
'fault_script'
),
'BACKUP'
:
sync_group
.
get
(
'backup_script'
),
'MASTER'
:
sync_group
.
get
(
'master_script'
)
}
logger
.
debug
(
"Loaded configuration: {}"
.
format
(
self
.
vrrp_config
))
except
Exception
as
err
:
logger
.
error
(
"Unable to load configuration: {}"
.
format
(
err
))
# run command
def
_run_command
(
self
,
command
):
logger
.
debug
(
"Running the command: {}"
.
format
(
command
))
try
:
cmd
(
command
)
except
OSError
as
err
:
logger
.
error
(
f
'Unable to execute command "{command}": {err}'
)
# create FIFO pipe
def
pipe_create
(
self
):
if
Path
(
self
.
pipe_path
)
.
exists
():
logger
.
info
(
"PIPE already exist: {}"
.
format
(
self
.
pipe_path
))
else
:
os
.
mkfifo
(
self
.
pipe_path
)
# process message from pipe
def
pipe_process
(
self
):
logger
.
debug
(
"Message processing start"
)
regex_notify
=
re
.
compile
(
r'^(?P<type>\w+) "(?P<name>[\w-]+)" (?P<state>\w+) (?P<priority>\d+)$'
,
re
.
MULTILINE
)
while
self
.
stopme
.
is_set
()
is
False
:
# wait for a new message event from pipe_wait
self
.
message_event
.
wait
()
try
:
# clear mesage event flag
self
.
message_event
.
clear
()
# get all messages from queue and try to process them
while
self
.
message_queue
.
empty
()
is
not
True
:
message
=
self
.
message_queue
.
get
()
logger
.
debug
(
"Received message: {}"
.
format
(
message
))
notify_message
=
regex_notify
.
search
(
message
)
# try to process a message if it looks valid
if
notify_message
:
n_type
=
notify_message
.
group
(
'type'
)
n_name
=
notify_message
.
group
(
'name'
)
n_state
=
notify_message
.
group
(
'state'
)
logger
.
info
(
"{} {} changed state to {}"
.
format
(
n_type
,
n_name
,
n_state
))
# check and run commands for VRRP instances
if
n_type
==
'INSTANCE'
:
if
n_name
in
self
.
vrrp_config
[
'vrrp_groups'
]
and
n_state
in
self
.
vrrp_config
[
'vrrp_groups'
][
n_name
]:
n_script
=
self
.
vrrp_config
[
'vrrp_groups'
][
n_name
]
.
get
(
n_state
)
if
n_script
:
self
.
_run_command
(
n_script
)
# check and run commands for VRRP sync groups
# currently, this is not available in VyOS CLI
if
n_type
==
'GROUP'
:
if
n_name
in
self
.
vrrp_config
[
'sync_groups'
]
and
n_state
in
self
.
vrrp_config
[
'sync_groups'
][
n_name
]:
n_script
=
self
.
vrrp_config
[
'sync_groups'
][
n_name
]
.
get
(
n_state
)
if
n_script
:
self
.
_run_command
(
n_script
)
# mark task in queue as done
self
.
message_queue
.
task_done
()
except
Exception
as
err
:
logger
.
error
(
"Error processing message: {}"
.
format
(
err
))
logger
.
debug
(
"Terminating messages processing thread"
)
# wait for messages
def
pipe_wait
(
self
):
logger
.
debug
(
"Message reading start"
)
self
.
pipe_read
=
os
.
open
(
self
.
pipe_path
,
os
.
O_RDONLY
|
os
.
O_NONBLOCK
)
while
self
.
stopme
.
is_set
()
is
False
:
# sleep a bit to not produce 100% CPU load
time
.
sleep
(
0.1
)
try
:
# try to read a message from PIPE
message
=
os
.
read
(
self
.
pipe_read
,
500
)
if
message
:
# split PIPE content by lines and put them into queue
for
line
in
message
.
decode
()
.
strip
()
.
splitlines
():
self
.
message_queue
.
put
(
line
)
# set new message flag to start processing
self
.
message_event
.
set
()
except
Exception
as
err
:
# ignore the "Resource temporarily unavailable" error
if
err
.
errno
!=
11
:
logger
.
error
(
"Error receiving message: {}"
.
format
(
err
))
logger
.
debug
(
"Closing FIFO pipe"
)
os
.
close
(
self
.
pipe_read
)
# handle SIGTERM signal to allow finish all messages processing
def
sigterm_handle
(
signum
,
frame
):
logger
.
info
(
"Ending processing: Received SIGTERM signal"
)
fifo
.
stopme
.
set
()
thread_wait_message
.
join
()
fifo
.
message_event
.
set
()
thread_process_message
.
join
()
signal
.
signal
(
signal
.
SIGTERM
,
sigterm_handle
)
# init our class
fifo
=
KeepalivedFifo
()
# try to create PIPE if it is not exist yet
# It looks like keepalived do it before the script will be running, but if we
# will decide to run this not from keepalived config, then we may get in
# trouble. So it is betteer to leave this here.
fifo
.
pipe_create
()
# create and run dedicated threads for reading and processing messages
thread_wait_message
=
threading
.
Thread
(
target
=
fifo
.
pipe_wait
)
thread_process_message
=
threading
.
Thread
(
target
=
fifo
.
pipe_process
)
thread_wait_message
.
start
()
thread_process_message
.
start
()
File Metadata
Details
Attached
Mime Type
text/x-script.python
Expires
Sat, Oct 17, 12:16 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4350577
Default Alt Text
keepalived-fifo.py (8 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment