Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F35450775
high-availability.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
high-availability.py
View Options
#!/usr/bin/env python3
#
# Copyright (C) 2018-2023 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
from
sys
import
exit
from
ipaddress
import
ip_interface
from
ipaddress
import
IPv4Interface
from
ipaddress
import
IPv6Interface
from
vyos.base
import
Warning
from
vyos.config
import
Config
from
vyos.configdict
import
leaf_node_changed
from
vyos.ifconfig.vrrp
import
VRRP
from
vyos.template
import
render
from
vyos.template
import
is_ipv4
from
vyos.template
import
is_ipv6
from
vyos.utils.network
import
is_ipv6_tentative
from
vyos.utils.process
import
call
from
vyos
import
ConfigError
from
vyos
import
airbag
airbag
.
enable
()
systemd_override
=
r'/run/systemd/system/keepalived.service.d/10-override.conf'
def
get_config
(
config
=
None
):
if
config
:
conf
=
config
else
:
conf
=
Config
()
base
=
[
'high-availability'
]
if
not
conf
.
exists
(
base
):
return
None
ha
=
conf
.
get_config_dict
(
base
,
key_mangling
=
(
'-'
,
'_'
),
no_tag_node_value_mangle
=
True
,
get_first_key
=
True
,
with_defaults
=
True
)
## Get the sync group used for conntrack-sync
conntrack_path
=
[
'service'
,
'conntrack-sync'
,
'failover-mechanism'
,
'vrrp'
,
'sync-group'
]
if
conf
.
exists
(
conntrack_path
):
ha
[
'conntrack_sync_group'
]
=
conf
.
return_value
(
conntrack_path
)
if
leaf_node_changed
(
conf
,
base
+
[
'vrrp'
,
'snmp'
]):
ha
.
update
({
'restart_required'
:
{}})
return
ha
def
verify
(
ha
):
if
not
ha
or
'disable'
in
ha
:
return
None
used_vrid_if
=
[]
if
'vrrp'
in
ha
and
'group'
in
ha
[
'vrrp'
]:
for
group
,
group_config
in
ha
[
'vrrp'
][
'group'
]
.
items
():
# Check required fields
if
'vrid'
not
in
group_config
:
raise
ConfigError
(
f
'VRID is required but not set in VRRP group "{group}"'
)
if
'interface'
not
in
group_config
:
raise
ConfigError
(
f
'Interface is required but not set in VRRP group "{group}"'
)
if
'address'
not
in
group_config
:
raise
ConfigError
(
f
'Virtual IP address is required but not set in VRRP group "{group}"'
)
if
'authentication'
in
group_config
:
if
not
{
'password'
,
'type'
}
<=
set
(
group_config
[
'authentication'
]):
raise
ConfigError
(
f
'Authentication requires both type and passwortd to be set in VRRP group "{group}"'
)
if
'health_check'
in
group_config
:
_validate_health_check
(
group
,
group_config
)
# Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction
# We also need to make sure VRID is not used twice on the same interface with the
# same address family.
interface
=
group_config
[
'interface'
]
vrid
=
group_config
[
'vrid'
]
# 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
:
ip_interface
(
i
),
group_config
[
'address'
]))
vaddrs4
=
list
(
filter
(
lambda
x
:
isinstance
(
x
,
IPv4Interface
),
vaddrs
))
vaddrs6
=
list
(
filter
(
lambda
x
:
isinstance
(
x
,
IPv6Interface
),
vaddrs
))
if
vaddrs4
and
vaddrs6
:
raise
ConfigError
(
f
'VRRP group "{group}" mixes IPv4 and IPv6 virtual addresses, this is not allowed.
\n
'
\
'Create individual groups for IPv4 and IPv6!'
)
if
vaddrs4
:
tmp
=
{
'interface'
:
interface
,
'vrid'
:
vrid
,
'ipver'
:
'IPv4'
}
if
tmp
in
used_vrid_if
:
raise
ConfigError
(
f
'VRID "{vrid}" can only be used once on interface "{interface} with address family IPv4"!'
)
used_vrid_if
.
append
(
tmp
)
if
'hello_source_address'
in
group_config
:
if
is_ipv6
(
group_config
[
'hello_source_address'
]):
raise
ConfigError
(
f
'VRRP group "{group}" uses IPv4 but hello-source-address is IPv6!'
)
if
'peer_address'
in
group_config
:
for
peer_address
in
group_config
[
'peer_address'
]:
if
is_ipv6
(
peer_address
):
raise
ConfigError
(
f
'VRRP group "{group}" uses IPv4 but peer-address is IPv6!'
)
if
vaddrs6
:
tmp
=
{
'interface'
:
interface
,
'vrid'
:
vrid
,
'ipver'
:
'IPv6'
}
if
tmp
in
used_vrid_if
:
raise
ConfigError
(
f
'VRID "{vrid}" can only be used once on interface "{interface} with address family IPv6"!'
)
used_vrid_if
.
append
(
tmp
)
if
'hello_source_address'
in
group_config
:
if
is_ipv4
(
group_config
[
'hello_source_address'
]):
raise
ConfigError
(
f
'VRRP group "{group}" uses IPv6 but hello-source-address is IPv4!'
)
if
'peer_address'
in
group_config
:
for
peer_address
in
group_config
[
'peer_address'
]:
if
is_ipv4
(
peer_address
):
raise
ConfigError
(
f
'VRRP group "{group}" uses IPv6 but peer-address is IPv4!'
)
# Check sync groups
if
'vrrp'
in
ha
and
'sync_group'
in
ha
[
'vrrp'
]:
for
sync_group
,
sync_config
in
ha
[
'vrrp'
][
'sync_group'
]
.
items
():
if
'health_check'
in
sync_config
:
_validate_health_check
(
sync_group
,
sync_config
)
if
'member'
in
sync_config
:
for
member
in
sync_config
[
'member'
]:
if
member
not
in
ha
[
'vrrp'
][
'group'
]:
raise
ConfigError
(
f
'VRRP sync-group "{sync_group}" refers to VRRP group "{member}", '
\
'but it does not exist!'
)
else
:
ha
[
'vrrp'
][
'group'
][
member
][
'_is_sync_group_member'
]
=
True
if
ha
[
'vrrp'
][
'group'
][
member
]
.
get
(
'health_check'
)
is
not
None
:
raise
ConfigError
(
f
'Health check configuration for VRRP group "{member}" will remain unused '
f
'while it has member of sync group "{sync_group}" '
f
'Only sync group health check will be used'
)
# Virtual-server
if
'virtual_server'
in
ha
:
for
vs
,
vs_config
in
ha
[
'virtual_server'
]
.
items
():
if
'address'
not
in
vs_config
and
'fwmark'
not
in
vs_config
:
raise
ConfigError
(
'Either address or fwmark is required '
f
'but not set for virtual-server "{vs}"'
)
if
'port'
not
in
vs_config
and
'fwmark'
not
in
vs_config
:
raise
ConfigError
(
f
'Port or fwmark is required but not set for virtual-server "{vs}"'
)
if
'port'
in
vs_config
and
'fwmark'
in
vs_config
:
raise
ConfigError
(
f
'Cannot set both port and fwmark for virtual-server "{vs}"'
)
if
'real_server'
not
in
vs_config
:
raise
ConfigError
(
f
'Real-server ip is required but not set for virtual-server "{vs}"'
)
# Real-server
for
rs
,
rs_config
in
vs_config
[
'real_server'
]
.
items
():
if
'port'
not
in
rs_config
:
raise
ConfigError
(
f
'Port is required but not set for virtual-server "{vs}" real-server "{rs}"'
)
def
_validate_health_check
(
group
,
group_config
):
health_check_types
=
[
"script"
,
"ping"
]
from
vyos.utils.dict
import
check_mutually_exclusive_options
try
:
check_mutually_exclusive_options
(
group_config
[
"health_check"
],
health_check_types
,
required
=
True
)
except
ValueError
:
Warning
(
f
'Health check configuration for VRRP group "{group}" will remain unused '
\
f
'until it has one of the following options: {health_check_types}'
)
# XXX: health check has default options so we need to remove it
# to avoid generating useless config statements in keepalived.conf
del
group_config
[
"health_check"
]
def
generate
(
ha
):
if
not
ha
or
'disable'
in
ha
:
if
os
.
path
.
isfile
(
systemd_override
):
os
.
unlink
(
systemd_override
)
return
None
render
(
VRRP
.
location
[
'config'
],
'high-availability/keepalived.conf.j2'
,
ha
)
render
(
systemd_override
,
'high-availability/10-override.conf.j2'
,
ha
)
return
None
def
apply
(
ha
):
service_name
=
'keepalived.service'
call
(
'systemctl daemon-reload'
)
if
not
ha
or
'disable'
in
ha
:
call
(
f
'systemctl stop {service_name}'
)
return
None
# Check if IPv6 address is tentative T5533
for
group
,
group_config
in
ha
.
get
(
'vrrp'
,
{})
.
get
(
'group'
,
{})
.
items
():
if
'hello_source_address'
in
group_config
:
if
is_ipv6
(
group_config
[
'hello_source_address'
]):
ipv6_address
=
group_config
[
'hello_source_address'
]
interface
=
group_config
[
'interface'
]
checks
=
20
interval
=
0.1
for
_
in
range
(
checks
):
if
is_ipv6_tentative
(
interface
,
ipv6_address
):
time
.
sleep
(
interval
)
systemd_action
=
'reload-or-restart'
if
'restart_required'
in
ha
:
systemd_action
=
'restart'
call
(
f
'systemctl {systemd_action} {service_name}'
)
return
None
if
__name__
==
'__main__'
:
try
:
c
=
get_config
()
verify
(
c
)
generate
(
c
)
apply
(
c
)
except
ConfigError
as
e
:
print
(
e
)
exit
(
1
)
File Metadata
Details
Attached
Mime Type
text/x-script.python
Expires
Tue, Dec 9, 10:51 PM (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3070119
Default Alt Text
high-availability.py (9 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment