Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Jakub Čajka
e946fe8df5 Inital commit 2019-01-21 10:43:55 +01:00
10 changed files with 1041 additions and 0 deletions

32
Dockerfile Normal file
View file

@ -0,0 +1,32 @@
#
# VIP failover monitoring container for OpenShift.
#
# ImageName: openshift/origin-keepalived-ipfailover
#
FROM registry.fedoraproject.org/f29/origin-base:latest
ENV NAME=origin-keepalived-ipfailover \
VERSION=3.11 \
ARCH=x86_64
RUN INSTALL_PKGS="kmod keepalived iproute psmisc nmap-ncat net-tools ipset ipset-libs" && \
dnf install -y $INSTALL_PKGS && \
rpm -V $INSTALL_PKGS && \
dnf clean all
COPY . /var/lib/ipfailover/keepalived/
LABEL io.k8s.display-name="OpenShift IP Failover" \
io.k8s.description="This is a component of OpenShift and runs a clustered keepalived instance across multiple hosts to allow highly available IP addresses." \
io.openshift.tags="openshift,ha,ip,failover" \
summary="This is the base image from which all OpenShift Origin images inherit." \
maintainer="Jakub Cajka <jcajka@fedoraproject.org>" \
License="GPLv2+" \
name="$FGC/$NAME" \
com.redhat.component="$NAME" \
version="$VERSION" \
architecture="$ARCH" \
usage="This is the base image from which all OpenShift Origin images inherit."
EXPOSE 1985
WORKDIR /var/lib/ipfailover
ENTRYPOINT ["/var/lib/ipfailover/keepalived/monitor.sh"]

View file

@ -0,0 +1,101 @@
{
"kind": "Config",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "hello-openshift-service",
"creationTimestamp": null
},
"spec": {
"ports": [
{
"protocol": "TCP",
"port": 8080,
"targetPort": 0,
"nodePort": 0
}
],
"selector": {
"name": "hello-openshift-label"
},
"type": "ClusterIP",
"sessionAffinity": "None"
},
"status": {
"loadBalancer": {}
}
},
{
"kind": "ReplicationController",
"apiVersion": "v1",
"metadata": {
"name": "hello-openshift",
"creationTimestamp": null,
"labels": {
"name": "hello-openshift-label"
}
},
"spec": {
"replicas": 1,
"selector": {
"name": "hello-openshift-label"
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"name": "hello-openshift-label"
}
},
"spec": {
"containers": [
{
"name": "hello-openshift-container",
"image": "openshift/hello-openshift",
"ports": [
{
"containerPort": 8080,
"protocol": "TCP"
}
],
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"imagePullPolicy": "IfNotPresent",
"capabilities": {},
"securityContext": {
"capabilities": {},
"privileged": false
}
}
],
"restartPolicy": "Always",
"dnsPolicy": "ClusterFirst"
}
}
},
"status": {
"replicas": 0
}
},
{
"kind": "Route",
"apiVersion": "v1",
"metadata": {
"name": "hello-openshift-route",
"creationTimestamp": null
},
"spec": {
"host": "hello.openshift.test",
"to": {
"kind": "Service",
"name": "hello-openshift-service"
}
},
"status": {}
}
]
}

111
conf/settings.sh Executable file
View file

@ -0,0 +1,111 @@
#!/bin/bash
# ========================================================================
# Settings passed by the failover coordinator on OpenShift Origin.
# ========================================================================
# Name of this IP Failover config instance.
HA_CONFIG_NAME="${OPENSHIFT_HA_CONFIG_NAME:-"OpenShift-IPFailover"}"
# IP Failover config selector.
HA_SELECTOR="${OPENSHIFT_HA_SELECTOR:-""}"
# List of virtual IP addresses.
#
# The value entries are comma-separated entries of the form:
# <ipaddress-range|ipaddress>
#
# where: ipaddress-range = <start-ipaddress>-<endip>
#
# Example:
# OPENSHIFT_HA_VIRTUAL_IPS="10.42.42.42,10.100.1.20-24"
#
HA_VIPS="${OPENSHIFT_HA_VIRTUAL_IPS:-""}"
HA_VIP_GROUPS="${OPENSHIFT_HA_VIP_GROUPS:-""}"
# Interface (ethernet) to use - bound by vrrp.
NETWORK_INTERFACE="${OPENSHIFT_HA_NETWORK_INTERFACE:-""}" # "enp0s8"
# Service port to monitor for failover.
HA_MONITOR_PORT="${OPENSHIFT_HA_MONITOR_PORT:-"80"}"
# Number of initial replicas.
HA_REPLICA_COUNT="${OPENSHIFT_HA_REPLICA_COUNT:-"1"}"
# Offset value to use to set the virtual router ids. Using different offset
# values allows multiple ipfailover configurations to exist within the
# same cluster. Range 1..255
# HA_VRRP_ID_OFFSET=30
#
HA_VRRP_ID_OFFSET="${OPENSHIFT_HA_VRRP_ID_OFFSET:-"0"}"
# When the DC supplies an (non null) iptables chain
# (OPENSHIFT_HA_IPTABLES_CHAIN) make sure the rule to pass keepalived
# multicast (224.0.0.18) traffic is in the table.
HA_IPTABLES_CHAIN="${OPENSHIFT_HA_IPTABLES_CHAIN:-""}"
# Optional external check script that is run every HA_CHECK_INTERVAL seconds
# The script can test whatever is needed to verify the application is running.
# Must return 0 -- OK, or 1 -- fail
# This script is in addition to the default check that the port is listening.
# The script must be accessible from inside the keepalived pod
HA_CHECK_SCRIPT="${OPENSHIFT_HA_CHECK_SCRIPT:-""}"
# Optional notify script is called when a state transition occurs
# Transition to MASTER, or to BACKUP, or to FAULT
# The parameters to the script are passed in by keepalived:
# $1 - "GROUP"|"INSTANCE"
# $2 - name of group or instance
# $3 - target state of transition ("MASTER"|"BACKUP"|"FAULT")
# The script must be accessible from inside the keepalived pod
HA_NOTIFY_SCRIPT="${OPENSHIFT_HA_NOTIFY_SCRIPT:-""}"
# The check script is run every HA_CHECK_INTERVAL seconds.
# Default is 2
HA_CHECK_INTERVAL="${OPENSHIFT_HA_CHECK_INTERVAL:-"2"}"
# VRRP will preempt a lower priority machine when a higher priority one
# comes back online. You can change the preemption strategy to either:
# "nopreempt" - which allows the lower priority machine to maintain its
# 'MASTER' status.
# OR
# "preempt_delay 300" - waits 5 mins (in seconds) after startup to
# preempt lower priority MASTERs.
PREEMPTION="${OPENSHIFT_HA_PREEMPTION:-"preempt_delay 300"}"
# ========================================================================
# Default settings - not currently exposed or overridden on OpenShift.
# ========================================================================
# If your environment doesn't support multicast, you can send VRRP adverts
# to a list of IPv{4,6} addresses using unicast.
# Example:
# UNICAST_PEERS="5.6.7.8,9.10.11.12,13.14.15.16"
UNICAST_PEERS="${OPENSHIFT_HA_UNICAST_PEERS:-""}"
# List of emails to send admin messages to. If the list of email ids is
# too long, you can use a DL (distribution list) ala:
# ADMIN_EMAILS=("ramr@redhat.com" "cops@acme.org")
ADMIN_EMAILS=(${OPENSHIFT_HA_ADMIN_EMAILS:-"root@localhost"})
# Email sender - the from address in the email headers.
EMAIL_FROM="ipfailover@openshift.local"
# IP address of the SMTP server.
SMTP_SERVER="${OPENSHIFT_HA_SMTP_SERVER:-"127.0.0.1"}"
# SMTP connect timeout (in seconds).
SMTP_CONNECT_TIMEOUT=30
# By default, the IP for binding vrrpd is the primary IP on the above
# specified interface. If you want to hide the location of vrrpd, you can
# specify a src_addr for multicast/unicast vrrp packets.
# MULTICAST_SOURCE_IPADDRESS="1.2.3.4"
# UNICAST_SOURCE_IPADDRESS="1.2.3.4"

297
lib/config-generators.sh Executable file
View file

@ -0,0 +1,297 @@
#!/bin/bash
# TODO: This follows the initial demo pieces and uses a bash script to
# generate the keepalived config - rework this into a template
# similar to how it is done for the haproxy configuration.
# Includes.
source "$(dirname "${BASH_SOURCE[0]}")/utils.sh"
# Constants.
readonly CHECK_SCRIPT_NAME="chk_${HA_CONFIG_NAME//-/_}"
readonly CHECK_INTERVAL_SECS="${HA_CHECK_INTERVAL}"
readonly VRRP_SLAVE_PRIORITY=42
readonly DEFAULT_PREEMPTION_STRATEGY="preempt_delay 300"
#
# Generate global config section.
#
# Example:
# generate_global_config arparp
#
function generate_global_config() {
local routername ; routername=$(scrub "$1")
echo "global_defs {"
echo " notification_email {"
for email in ${ADMIN_EMAILS[@]}; do
echo " $email"
done
echo " }"
echo ""
echo " notification_email_from ${EMAIL_FROM:-"ipfailover@openshift.local"}"
echo " smtp_server ${SMTP_SERVER:-"127.0.0.1"}"
echo " smtp_connect_timeout ${SMTP_CONNECT_TIMEOUT:-"30"}"
echo " router_id ${routername}"
echo "}"
}
#
# Generate VRRP checker script configuration section.
# When a check script is provided use it instead of default script
# The default script is suppressed When port is 0
#
# Example:
# generate_script_config
# generate_script_config "10.1.2.3" 8080
#
function generate_script_config() {
local serviceip ; serviceip=${1:-"127.0.0.1"}
local port=${2:-80}
echo ""
echo "vrrp_script ${CHECK_SCRIPT_NAME} {"
if [[ -n "${HA_CHECK_SCRIPT}" ]]; then
echo " script \"${HA_CHECK_SCRIPT}\""
else
if [[ "${port}" == "0" ]]; then
echo " script \"true\""
else
echo " script \"/bin/bash -c '</dev/tcp/${serviceip}/${port}'\""
fi
fi
echo " interval ${CHECK_INTERVAL_SECS}"
echo "}"
}
#
# Generate authentication information section.
#
# Example:
# generate_authentication_info
#
function generate_authentication_info() {
local creds=${1:-"R0ut3r"}
echo ""
echo " authentication {"
echo " auth_type PASS"
echo " auth_pass ${creds}"
echo " }"
}
#
# Generate track script section.
#
# Example:
# generate_track_script
#
function generate_track_script() {
echo ""
echo " track_script {"
echo " ${CHECK_SCRIPT_NAME}"
echo " }"
}
#
# Generate multicast + unicast options section based on the values of the
# MULTICAST_SOURCE_IPADDRESS, UNICAST_SOURCE_IPADDRESS and UNICAST_PEERS
# environment variables.
#
# Examples:
# generate_mucast_options
#
# UNICAST_SOURCE_IPADDRESS=10.1.1.1 UNICAST_PEERS="10.1.1.2,10.1.1.3" \
# generate_mucast_options
#
function generate_mucast_options() {
echo ""
if [[ -n "${MULTICAST_SOURCE_IPADDRESS}" ]]; then
echo " mcast_src_ip ${MULTICAST_SOURCE_IPADDRESS}"
fi
if [[ -n "${UNICAST_SOURCE_IPADDRESS}" ]]; then
echo " unicast_src_ip ${UNICAST_SOURCE_IPADDRESS}"
fi
if [[ -n "${UNICAST_PEERS}" ]]; then
echo ""
echo " unicast_peer {"
OLD_IFS=$IFS
IFS=","
for ip in ${UNICAST_PEERS}; do
echo " ${ip}"
done
IFS=$OLD_IFS
echo " }"
fi
}
#
# Generate virtual ip address section.
#
# Examples:
# generate_vip_section "10.245.2.3" "enp0s8"
#
# generate_vip_section "10.1.1.1 10.1.2.2" "enp0s8"
#
# generate_vip_section "10.42.42.42-45, 10.9.1.1"
#
function generate_vip_section() {
local interface ; interface=${2:-"$(get_network_device)"}
echo ""
echo " virtual_ipaddress {"
for ip in ${1}; do
echo " ${ip} dev ${interface}"
done
echo " }"
}
#
# Generate vrrpd instance configuration section.
#
# Examples:
# generate_vrrpd_instance_config arp 1 "10.1.2.3" enp0s8 "252" "master"
#
# generate_vrrpd_instance_config arp 1 "10.1.2.3" enp0s8 "3" "slave"
#
# generate_vrrpd_instance_config ipf-1 4 "10.1.2.3-4" enp0s8 "7"
#
function generate_vrrpd_instance_config() {
local servicename=$1
local iid=${2:-"1"}
local vips=$3
local interface=$4
local priority=${5:-"10"}
local instancetype=${6:-"slave"}
local vipname ; vipname=$(scrub "$1")
local initialstate=""
local preempt=${PREEMPTION:-"${DEFAULT_PREEMPTION_STRATEGY}"}
local vrrpidoffset=${HA_VRRP_ID_OFFSET:-0}
[ "${instancetype}" = "master" ] && initialstate="state MASTER"
local instance_name ; instance_name=$(generate_vrrp_instance_name "${servicename}" "${iid}")
local auth_section ; auth_section=$(generate_authentication_info "${servicename}")
local vip_section ; vip_section=$(generate_vip_section "${vips}" "${interface}")
# Emit instance
echo "
vrrp_instance ${instance_name} {
interface ${interface}
${initialstate}
virtual_router_id $((vrrpidoffset + iid))
priority ${priority}
${preempt}
${auth_section}
$(generate_track_script)
"
if [[ -n $HA_NOTIFY_SCRIPT ]]; then
echo " notify \"${HA_NOTIFY_SCRIPT}\""
fi
echo " $(generate_mucast_options)
${vip_section}
}
"
}
#
# Generate failover configuration.
#
# Examples:
# generate_failover_configuration
#
function generate_failover_config() {
local vips ; vips=$(expand_ip_ranges "${HA_VIPS}")
local vip_groups ; vip_groups="${HA_VIP_GROUPS}"
local interface ; interface=$(get_network_device "${NETWORK_INTERFACE}")
local ipaddr ; ipaddr=$(get_device_ip_address "${interface}")
local port="${HA_MONITOR_PORT//[^0-9]/}"
echo "! Configuration File for keepalived
$(generate_global_config "${HA_CONFIG_NAME}")
$(generate_script_config "${ipaddr}" "${port}")
"
local ipkey ; ipkey=$(echo "${ipaddr}" | cut -f 4 -d '.')
local ipslot=$((ipkey % 128))
local nodecount
if [[ "${HA_REPLICA_COUNT}" -gt 0 ]]; then
nodecount="${HA_REPLICA_COUNT}"
else
nodecount="1"
fi
local idx=$((ipslot % nodecount))
idx=$((idx + 1))
local counter=1
local previous="none"
local vip_counter=0
local total_vips=( $vips )
local vips_per_group=1
local vips_mod=0
if [[ $vip_groups -gt 0 ]]; then
vips_per_group=$((${#total_vips[@]} / vip_groups))
vips_mod=$((${#total_vips[@]} % vip_groups))
fi
while [[ "${vip_counter}" -lt "${#total_vips[@]}" ]]; do
local cur_vip_count=vips_per_group
if [[ ${vips_mod} -gt 0 ]]; then
((cur_vip_count++))
((vips_mod--))
fi
vip_group=("${total_vips[@]:vip_counter:cur_vip_count}")
vip_counter=$((vip_counter + cur_vip_count))
local offset=$((RANDOM % 32))
local priority=$((ipslot % 64 + offset))
local instancetype="slave"
local n=$((counter % idx))
if [[ ${n} -eq 0 ]]; then
instancetype="master"
if [[ "${previous}" == "master" ]]; then
# Inverse priority + reset, so that we can flip-flop priorities.
priority=$((ipslot + 1))
previous="flip-flop"
else
priority=$((255 - ipslot))
previous=${instancetype}
fi
fi
generate_vrrpd_instance_config "${HA_CONFIG_NAME}" "${counter}" "${vip_group[*]}" \
"${interface}" "${priority}" "${instancetype}"
((counter++))
done
}

79
lib/failover-functions.sh Executable file
View file

@ -0,0 +1,79 @@
#!/bin/bash
# Includes.
mydir=$(dirname "${BASH_SOURCE[0]}")
source "$mydir/../conf/settings.sh"
source "$mydir/utils.sh"
source "$mydir/config-generators.sh"
# Constants.
readonly KEEPALIVED_CONFIG=${KEEPALIVED_CONFIG:-"/etc/keepalived/keepalived.conf"}
readonly KEEPALIVED_DEFAULTS="/etc/sysconfig/keepalived"
function cleanup() {
echo " - Cleaning up ... "
[ -n "$1" ] && kill -TERM $1
local interface=$(get_network_device "$NETWORK_INTERFACE")
local vips=$(expand_ip_ranges "$HA_VIPS")
echo " - Releasing VIPs ${vips} (interface ${interface}) ... "
local regex='^.*?/[0-9]+$'
for vip in ${vips}; do
echo " - Releasing VIP ${vip} ... "
if [[ ${vip} =~ ${regex} ]] ; then
ip addr del ${vip} dev ${interface} || :
else
ip addr del ${vip}/32 dev ${interface} || :
fi
done
exit 0
}
function setup_failover() {
echo " - Loading ip_vs module ..."
modprobe ip_vs
echo " - Checking if ip_vs module is available ..."
if lsmod | grep '^ip_vs'; then
echo " - Module ip_vs is loaded."
else
echo "ERROR: Module ip_vs is NOT available."
fi
# When the DC supplies an (non null) iptables chain
# (OPENSHIFT_HA_IPTABLES_CHAIN) make sure the rule to pass keepalived
# multicast (224.0.0.18) traffic is in the table.
chain="${HA_IPTABLES_CHAIN:-""}"
if [[ -n ${chain} ]]; then
echo " - check for iptables rule for keepalived multicast (224.0.0.18) ..."
if ! iptables -S | grep 224.0.0.18 > /dev/null 2>&1 ; then
# Add the rule to the beginning of the chain.
echo " - adding iptables rule to $chain to access 224.0.0.18."
iptables -I ${chain} 1 -d 224.0.0.18/32 -j ACCEPT
fi
fi
echo " - Generating and writing config to $KEEPALIVED_CONFIG"
generate_failover_config > "$KEEPALIVED_CONFIG"
}
function start_failover_services() {
echo " - Starting failover services ..."
[ -f "$KEEPALIVED_DEFAULTS" ] && source "$KEEPALIVED_DEFAULTS"
killall -9 /usr/sbin/keepalived &> /dev/null || :
/usr/sbin/keepalived $KEEPALIVED_OPTIONS -n --log-console &
local pid=$!
trap "cleanup ${pid}" SIGHUP SIGINT SIGTERM
wait ${pid}
}

269
lib/utils.sh Executable file
View file

@ -0,0 +1,269 @@
#!/bin/bash
# Constants.
LIB_DIR=$(dirname "${BASH_SOURCE[0]}")
VBOX_INTERFACES="enp0s3 enp0s8 eth1"
#
# Returns "scrubbed" name - removes characters that are not alphanumeric or
# underscore and replacing dashes with underscores.
#
# Examples:
# scrub "config\!@#@$%$^&*()-+=1_{}|[]\\:;'<>?,./ipfailover"
# # -> config_1_ipfailover
#
# scrub "ha-1" # -> ha_1
#
function scrub() {
local val=$(echo "$1" | tr -dc '[:alnum:]\-_')
echo "${val//-/_}"
}
#
# Tests if an IPv4 address is valid
# Echos true (0) if valid, false (1) if invalid
#
# Examples:
# validate_ipv4 192.0.2.3
# # -> 0
#
# validate_ipv4 192.0.3.4.0
# # -> 1
#
# validate_ipv4 192.0.2
# # -> 1
#
function validate_ipv4() {
local IPv4_GROUP="[0-2]?[0-9]{1,2}"
local IPv4_SHAPE="^(${IPv4_GROUP}\.){3,3}(${IPv4_GROUP})$"
local is_valid=0
local i
if [[ ${1} =~ ${IPv4_SHAPE} ]]; then
for i in $(echo ${1} | tr "." " "); do
if [ $i -gt 255 ]; then
is_valid=1
fi
done
else
is_valid=1
fi
return ${is_valid}
}
#
# Tests if an IPv6 address is valid
# Returns true (0) if valid, false (1) if invalid
#
# Examples:
# validate_ipv6 2001:DB8:1:E32:FFFF:3:19:39FB
# # -> 0
#
# validate_ipv6 2001:DB8::39FB
# # -> 0
#
# validate_ipv6 2001::DB8::39FB
# # -> 1
#
function validate_ipv6() {
local IPv6_GROUP="[[:xdigit:]]{1,4}"
local IPv6_SHAPE1="^(${IPv6_GROUP}:){7,7}(${IPv6_GROUP})$"
local IPv6_SHAPE2="^::(${IPv6_GROUP}:){0,6}(${IPv6_GROUP})$"
local IPv6_SHAPE3="^(${IPv6_GROUP}:){0,6}:(${IPv6_GROUP}:){0,6}(${IPv6_GROUP})$"
local VALID_SHAPE3="^(${IPv6_GROUP}:+){1,6}(${IPv6_GROUP})$"
local is_valid=1
if [[ ${1} =~ ${IPv6_SHAPE1} ]]; then
is_valid=0
fi
if [[ ${1} =~ ${IPv6_SHAPE2} ]]; then
is_valid=0
fi
if [[ ${1} =~ ${IPv6_SHAPE3} ]]; then
if [[ ${1} =~ ${VALID_SHAPE3} ]]; then
is_valid=0
fi
fi
return ${is_valid}
}
#
# Expands list of IPv4 addresses. List elements can be an IP address
# range or an IP address.
#
# Examples:
# expand_ipv4_range "3.3.3.3-4"
# # -> 3.3.3.3 3.3.3.4
#
# expand_ipv4_range "10.1.1.100-100"
# # -> 10.1.1.100
#
# expand_ipv4_range "10.1.1.100"
# # -> 10.1.1.100
#
function expand_ipv4_range() {
local expandedset=()
local ip1=$(echo "$1" | awk '{print $1}' FS='-')
local ip2=$(echo "$1" | awk '{print $2}' FS='-')
local n
if [ -z "$ip2" ]; then
expandedset=(${expandedset[@]} "$ip1")
else
local base=$(echo "$ip1" | cut -f 1-3 -d '.')
local start=$(echo "$ip1" | awk '{print $NF}' FS='.')
local end=$(echo "$ip2" | awk '{print $NF}' FS='.')
for n in `seq $start $end`; do
expandedset=(${expandedset[@]} "${base}.$n")
done
fi
echo "${expandedset[@]}"
}
#
# Expands list of IPv6 addresses. List elements can be an IP address
# range or an IP address.
#
# Examples:
# expand_ipv6_range "2001:DB8:1ABC::1F39-1F3B"
# # -> 2001:DB8:1ABC::1F39 2001:DB8:1ABC::1F3A 2001:DB8:1ABC::1F3B
#
function expand_ipv6_range() {
local expandedset=()
local ip1=$(echo "$1" | awk '{print $1}' FS='-')
local ip2=$(echo "$1" | awk '{print $2}' FS='-')
local n
if [ -z "$ip2" ]; then
expandedset=(${expandedset[@]} "$ip1")
else
local start=${ip1##*:}
local decstart=`echo "ibase=16; ${start}" | bc`
local base=${ip1%%${start}}
local decend=`echo "ibase=16; ${ip2}" | bc`
for n in `seq $decstart $decend`; do
end=`echo "obase=16; ${n}" | bc`
expandedset=(${expandedset[@]} "${base}${end}")
done
fi
echo "${expandedset[@]}"
}
#
# Returns the IP address family (IPv4 or IPv6)
# Returns "4" or "6" respectively
#
# Examples:
# get_address_family "192.168.3.1"
# # -> 4
#
# get_address_family "2001:DB8:1ABC::1F3A"
# # -> 6
#
function get_address_family() {
if validate_ipv4 ${1}; then
return 4
elif validate_ipv6 ${1}; then
return 6
else
return 1
fi
}
#
# Expands list of virtual IP addresses. List elements can be an IP address
# range or an IP address and elements can be space or comma separated.
#
# Examples:
# expand_ip_ranges "1.1.1.1, 2.2.2.2,3.3.3.3-4 4.4.4.4"
# # -> 1.1.1.1 2.2.2.2 3.3.3.3 3.3.3.4 4.4.4.4
#
# expand_ip_ranges "10.1.1.100-102 10.1.1.200-200 10.42.42.42"
# # -> 10.1.1.100 10.1.1.101 10.1.1.102 10.1.1.200 10.42.42.42
#
function expand_ip_ranges() {
local vips=${1:-""}
local expandedset=()
local iprange
local newip
for iprange in $(echo "$vips" | sed 's/[^0-9a-fA-F:\.,-]//g' | tr "," " "); do
local ip1=$(echo "$iprange" | awk '{print $1}' FS='-')
get_address_family ${ip1}
local family=$?
if [ ${family} == "4" ]; then
for newip in $(expand_ipv4_range ${iprange}); do
expandedset=(${expandedset[@]} ${newip})
done
elif [ ${family} == "6" ]; then
for newip in $(expand_ipv6_range ${iprange}); do
expandedset=(${expandedset[@]} ${newip})
done
fi
done
echo "${expandedset[@]}"
}
#
# Generate base name for the VRRP instance.
#
# Examples:
# vrrp_instance_basename "arp" # -> arp_VIP
#
# vrrp_instance_basename "ha-1" # -> ha_1_VIP
#
function vrrp_instance_basename() {
echo "$(scrub "$1")_VIP"
}
#
# Generate VRRP instance name.
#
# Examples:
# generate_vrrp_instance_name arp 42 # -> arp_VIP_42
#
# generate_vrrp_instance_name ha-1 # -> ha_1_VIP_0
#
function generate_vrrp_instance_name() {
local iid=${2:-0}
echo "$(vrrp_instance_basename "$1")_${iid}"
}
#
# Returns the network device name to use for VRRP.
#
# Examples:
# get_network_device
#
# get_network_device "eth0"
#
function get_network_device() {
for dev in $1 ${VBOX_INTERFACES}; do
if ip addr show dev "$dev" &> /dev/null; then
echo "$dev"
return
fi
done
ip route get 8.8.8.8 | awk '/dev/ { f=NR }; f && (NR-1 == f)' RS=" "
}
#
# Returns the IP address associated with a network device.
#
# Examples:
# get_device_ip_address
#
# get_device_ip_address "docker0"
#
function get_device_ip_address() {
local dev=${1:-"$(get_network_device)"}
ifconfig "$dev" | awk '/inet / { print $2 }'
}

11
makefile.test Normal file
View file

@ -0,0 +1,11 @@
IMAGE_NAME="openshift/origin-keepalived-ipfailover"
all: image
test: image
(cd tests && ./verify_failover_image.sh)
image:
docker build -t $(IMAGE_NAME) .

15
monitor.sh Executable file
View file

@ -0,0 +1,15 @@
#!/bin/bash
# Includes.
source "$(dirname "${BASH_SOURCE[0]}")/lib/failover-functions.sh"
#
# main():
#
setup_failover
start_failover_services
echo "`basename $0`: OpenShift IP Failover service terminated."

36
tests/echoserver.py Normal file
View file

@ -0,0 +1,36 @@
#!/usr/bin/env python
""" Echo server - reply back with the received message. """
import os
import signal
import socket
import sys
def sigusr1_handler(signum, frame):
print 'signal %s received, exiting ...' % signum
sys.exit(0)
def setup():
signal.signal(signal.SIGUSR1, sigusr1_handler)
def runserver():
sock = socket.socket()
sock.bind(('0.0.0.0', int(os.environ.get('PORT', '12345'))))
sock.listen(10)
while True:
c, raddr = sock.accept()
try:
d = c.recv(4096)
c.send(d if d else '')
finally:
c.close()
if "__main__" == __name__:
setup()
runserver()

90
tests/verify_failover_image.sh Executable file
View file

@ -0,0 +1,90 @@
#!/bin/bash -e
# Constants.
readonly TEST_DIR=$(dirname "${BASH_SOURCE[0]}")
readonly FAILOVER_IMAGE="openshift/origin-keepalived-ipfailover"
readonly TEST_VIPS="10.0.2.100-102,2001:DB8:1ABC::1F39-1F3B"
readonly MONITOR_PORT="12345"
function stop_echo_server() {
local pid=$1
if [ -z "$pid" ]; then
pid=$(ps -e -opid,args | grep echoserver.py | grep -v grep | awk '{print $1}')
fi
# Send SIGUSR1 to the echo server to terminate it.
[ -n "$pid" ] && kill -s USR1 $pid
}
function start_echo_server() {
stop_echo_server
export PORT=${MONITOR_PORT}
nohup python ${TEST_DIR}/echoserver.py &> /dev/null &
echo $!
}
function start_failover_container() {
local cfg="-e OPENSHIFT_HA_CONFIG_NAME="roto-r00ter""
local vips="-e OPENSHIFT_HA_VIRTUAL_IPS="${TEST_VIPS}""
local netif="-e OPENSHIFT_HA_NETWORK_INTERFACE="enp0s3""
local port="-e OPENSHIFT_HA_MONITOR_PORT="${MONITOR_PORT}""
# local unicast="-e export OPENSHIFT_HA_USE_UNICAST="true""
# local unicastpeers="-e OPENSHIFT_HA_UNICAST_PEERS="127.0.0.1""
local selector="-e OPENSHIFT_HA_SELECTOR="""
local envopts="$cfg $vips $netif $port $unicast $unicastpeers $selector"
docker run -dit --net=host --privileged=true \
-v /lib/modules:/lib/modules $envopts $FAILOVER_IMAGE &
}
function run_image_verification_test() {
echo " - starting echo server ..."
local pid=$(start_echo_server)
echo " - started echo server pid=$pid ..."
# On interrupt, cleanup - stop echo server.
trap "stop_echo_server $pid" INT
local cname=$(start_failover_container)
echo " - started docker container $cname ..."
# Wait a bit for all the services to startup.
sleep 10
# Check container is up and has keepalived processes.
local cmd="ps -ef | grep '/usr/sbin/keepalived' | grep -v grep | wc -l"
local numprocs=$(echo "$cmd" | docker exec -i $cname /bin/bash)
# Stop echo server.
stop_echo_server $pid
if [[ -n "$numprocs" && $numprocs -gt 0 ]]; then
# Success - print info and kill the container.
echo " - There are $numprocs keepalived processes running"
echo " - Cleaning up docker containers ..."
docker rm -f $cname
echo " - All tests PASSED."
return 0
fi
# Failure - print info and dump logs (keep the docker container around
# for debugging).
echo " - There are $numprocs keepalived processes running"
echo " - logs from container $cname:"
docker logs $cname || :
echo " - Test FAILED."
exit 1
}
#
# main():
#
run_image_verification_test