#!/bin/bash # # Resource script for haproxy daemon with namespace support # # Description: Manages haproxy daemon as an OCF resource in # an High Availability setup inside a namespace # # HAProxy OCF script's Author: Mirantis # License: GNU General Public License (GPL) # # usage: $0 {start|stop|restart|status|monitor|validate-all|meta-data} # # The "start" arg starts haproxy. # # The "stop" arg stops it. # # OCF parameters: # OCF_RESKEY_ns # OCF_RESKEY_conffile # OCF_RESKEY_pidfile # OCF_RESKEY_binpath # OCF_RESKEY_extraconf # # OCF_RESKEY_host_interface # OCF_RESKEY_namespace_interface # OCF_RESKEY_host_ip # OCF_RESKEY_namespace_ip # OCF_RESKEY_network_mask # OCF_RESKEY_route_metric # # Note: This RA requires that the haproxy config files has a "pidfile" # entry so that it is able to act on the correct process ########################################################################## # Initialization: OCF_ROOT_default="/usr/lib/ocf" : ${OCF_ROOT=${OCF_ROOT_default}} : ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat} . ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs : ${OCF_FUEL_FUNCTIONS_DIR=${OCF_ROOT}/resource.d/fuel} . ${OCF_FUEL_FUNCTIONS_DIR}/ocf-fuel-funcs ########################################################################### OCF_RESKEY_ns_default="haproxy" OCF_RESKEY_conffile_default="/etc/haproxy/haproxy.cfg" OCF_RESKEY_pidfile_default="${HA_RSCTMP}/${__SCRIPT_NAME}/${__SCRIPT_NAME}.pid" OCF_RESKEY_binpath_default="/usr/sbin/haproxy" OCF_RESKEY_extraconf_default="" OCF_RESKEY_other_networks_default="" OCF_RESKEY_host_interface_default="hapr-host" OCF_RESKEY_namespace_interface_default="hapr-ns" OCF_RESKEY_host_ip_default="240.0.0.1" OCF_RESKEY_namespace_ip_default="240.0.0.2" OCF_RESKEY_network_mask_default="30" OCF_RESKEY_route_metric_default="10000" OCF_RESKEY_debug_default=false : ${HA_LOGTAG="ocf-ns_haproxy"} : ${HA_LOGFACILITY="daemon"} : ${OCF_RESKEY_ns=${OCF_RESKEY_ns_default}} : ${OCF_RESKEY_conffile=${OCF_RESKEY_conffile_default}} : ${OCF_RESKEY_pidfile=${OCF_RESKEY_pidfile_default}} : ${OCF_RESKEY_binpath=${OCF_RESKEY_binpath_default}} : ${OCF_RESKEY_extraconf=${OCF_RESKEY_extraconf_default}} : ${OCF_RESKEY_other_networks=${OCF_RESKEY_other_networks_default}} : ${OCF_RESKEY_host_interface=${OCF_RESKEY_host_interface_default}} : ${OCF_RESKEY_namespace_interface=${OCF_RESKEY_namespace_interface_default}} : ${OCF_RESKEY_host_ip=${OCF_RESKEY_host_ip_default}} : ${OCF_RESKEY_namespace_ip=${OCF_RESKEY_namespace_ip_default}} : ${OCF_RESKEY_network_mask=${OCF_RESKEY_network_mask_default}} : ${OCF_RESKEY_route_metric=${OCF_RESKEY_route_metric_default}} : ${OCF_RESKEY_debug=${OCF_RESKEY_debug_default}} USAGE="Usage: $0 {start|stop|restart|status|monitor|validate-all|meta-data}"; RUN_IN_NS="ip netns exec $OCF_RESKEY_ns " if [ -z "${OCF_RESKEY_ns}" ] ; then RUN='' else RUN="$RUN_IN_NS " fi ########################################################################## usage() { echo $USAGE >&2 } meta_data() { cat < 1.0 This script manages haproxy daemon with namespace support Manages an haproxy daemon inside an namespace This is a dummy parameter. Pacemaker needs it to enable reload operation for the resource Dummy parameter Name of network namespace. Should be present. Name of network namespace. The haproxy daemon configuration file name with full path. For example, "/etc/haproxy/haproxy.cfg" Configuration file name with full path The haproxy pid file path. For example, "/var/run/haproxy.pid" Full path to the haproxy pid file The haproxy binary path. For example, "/usr/sbin/haproxy" Full path to the haproxy binary Extra command line arguments to pass to haproxy. For example, "-f /etc/haproxy/shared.cfg" Extra command line arguments for haproxy Additional routes that should be added to this resource. Routes will be added via value namespace_interface. List of addtional routes to add routes for. The host part of the interface pair used to connect the namespace to the network For example, "hapr-host" The name of the host interface used for namespace The namespace part of the interface pair used to connect the namespace to the network For example, "hapr-ns" The name of the namespace interface used for namespace The IP address used by the host interface. Must be from the same subnet as namesapce IP and uses network_mask to determine subnet. Should not collide with any IP addresses already used in your network. For example, "240.0.0.1" Host interface IP address The IP address used by the namespace interface. Must be from the same subnet as host IP and uses network_mask to determine subnet. Should not collide with any IP addresses already used in your network. For example, "240.0.0.2" Namespace interface IP address The network mask length used to determine subnet of the host and the namspace interfaces. For example, "30" Network mask length The metric value of the default route set for the pipe link connecting namespace and host. It should be set to a large number to be higher then other default route metrics that could be set to override this default route. If other routes are set eithin the namespace thir metric should be smaller then this number if you want them to be used istead of this route. For example, "1000" Namespace default route metric The debug flag for haproxy. HAProxy RA debug flag END exit $OCF_SUCCESS } # Only match TCP packets with the SYN bit set and the ACK,RST and FIN bits cleared # and block them. Such packets are used to request TCP connection initiation. # Limit the block rule scope to the haproxy namespace to no affect other connections block_client_access() { # do not add temporary SYN blocking rule, if it is already exist # otherwise, try to add a blocking rule with max of 5 retries local tries=5 until $($RUN_IN_NS iptables -t filter -nvL | grep -q 'temporary SYN block') || [ $tries -eq 0 ]; do tries=$((tries-1)) ocf_run $RUN_IN_NS iptables -t filter -I INPUT -p tcp \ -m comment --comment 'temporary SYN block' --syn -j DROP sleep 1 done if [ $tries -eq 0 ]; then return $OCF_ERR_GENERIC else return $OCF_SUCCESS fi } # Unblock the blocking rule unblock_client_access() { # remove all temporary SYN blocking rules, if there are more than one exist for i in $($RUN_IN_NS iptables -t filter -nvL --line-numbers | awk '/temporary SYN block/ {print $1}'); do ocf_run $RUN_IN_NS iptables -t filter -D INPUT -p tcp \ -m comment --comment 'temporary SYN block' --syn -j DROP done } check_ns() { local LH="${LL} check_ns():" local ns=$(ip netns list | awk "/${OCF_RESKEY_ns}/ {print \$1}") ocf_log debug "${LH} recieved netns list: ${ns}" [ "${ns}" != "${OCF_RESKEY_ns}" ] && return $OCF_ERR_GENERIC return $OCF_SUCCESS } get_ns() { local rc local LH="${LL} get_ns():" check_ns && return $OCF_SUCCESS ocf_run ip netns add $OCF_RESKEY_ns rc=$? ocf_run $RUN_IN_NS /sbin/sysctl -w net.ipv4.ip_nonlocal_bind=1 ocf_run $RUN_IN_NS ip link set up dev lo ocf_log debug "${LH} added netns ${OCF_RESKEY_ns} and set up lo" return $rc } get_variables() { local LH="${LL} get_variables():" get_ns CONF_FILE="${OCF_RESKEY_conffile}" COMMAND="$RUN ${OCF_RESKEY_binpath}" if [ -n "${OCF_RESKEY_pidfile}" ]; then PIDFILE="${OCF_RESKEY_pidfile}" else PIDFILE=$(grep -v "#" ${CONF_FILE} | grep "pidfile" | sed 's/^[ \t]*pidfile[ \t]*//') fi ocf_log debug "${LH} set up variables and PIDFILE name" } set_ns_routing() { nsip() { ip netns exec "${OCF_RESKEY_ns}" ip ${@} } # create host-ns veth pair unless it's present ip link | grep -q '^[[:digit:]]\+:[[:space:]]\+'"${OCF_RESKEY_host_interface}"'[@:]' if [ $? -gt 0 ]; then ocf_log debug "Creating host interface: ${OCF_RESKEY_host_interface} and namespace interface: ${OCF_RESKEY_namespace_interface}" ocf_run ip link add "${OCF_RESKEY_host_interface}" type veth peer name "${OCF_RESKEY_namespace_interface}" fi # move the ns part to the namespace ip link | grep -q '^[[:digit:]]\+:[[:space:]]\+'"${OCF_RESKEY_namespace_interface}"'[@:]' if [ $? -eq 0 ]; then ocf_log debug "Moving interface: ${OCF_RESKEY_namespace_interface} to namespace: ${OCF_RESKEY_ns}" ocf_run ip link set dev "${OCF_RESKEY_namespace_interface}" netns "${OCF_RESKEY_ns}" fi # up the host part ocf_log debug "Bringing up host interface: ${OCF_RESKEY_host_interface}" ocf_run ip link set "${OCF_RESKEY_host_interface}" up # set host part's ip ip addr show dev "${OCF_RESKEY_host_interface}" | grep -q "inet ${OCF_RESKEY_host_ip}/${OCF_RESKEY_network_mask}" if [ $? -gt 0 ]; then ocf_log debug "Setting host interface: ${OCF_RESKEY_host_interface} IP to: ${OCF_RESKEY_host_ip}/${OCF_RESKEY_network_mask}" ocf_run ip addr add "${OCF_RESKEY_host_ip}/${OCF_RESKEY_network_mask}" dev "${OCF_RESKEY_host_interface}" fi # up the ns part ocf_log debug "Bringing up the namespace interface: ${OCF_RESKEY_namespace_interface}" ocf_run nsip link set "${OCF_RESKEY_namespace_interface}" up # set ns part's ip nsip addr show dev "${OCF_RESKEY_namespace_interface}" | grep -q "inet ${OCF_RESKEY_namespace_ip}/${OCF_RESKEY_network_mask}" if [ $? -gt 0 ]; then ocf_log debug "Setting namespace interface: ${OCF_RESKEY_namespace_interface} IP to: ${OCF_RESKEY_namespace_ip}/${OCF_RESKEY_network_mask}" ocf_run nsip addr add "${OCF_RESKEY_namespace_ip}/${OCF_RESKEY_network_mask}" dev "${OCF_RESKEY_namespace_interface}" fi # set default gateway inside ns nsip route list | grep -q "default via ${OCF_RESKEY_host_ip}" if [ $? -gt 0 ]; then ocf_log debug "Creating default route inside the namespace to ${OCF_RESKEY_host_ip} with metric ${OCF_RESKEY_route_metric}" ocf_run nsip route add default via "${OCF_RESKEY_host_ip}" metric "${OCF_RESKEY_route_metric}" fi # set masquerade on host node iptables -n -t nat -L | grep -q masquerade-for-haproxy-namespace if [ $? -gt 0 ]; then ocf_log debug "Creating NAT rule on the host system for traffic from IP: ${OCF_RESKEY_namespace_ip}" ocf_run iptables -t nat -A POSTROUTING -s "${OCF_RESKEY_namespace_ip}" -j MASQUERADE -m comment --comment "masquerade-for-haproxy-namespace" fi ### Needed for ML2 routing ### ocf_run sysctl -w net.ipv4.conf.${OCF_RESKEY_host_interface}.rp_filter=2 ocf_run $RUN_IN_NS sysctl -w net.ipv4.conf.all.rp_filter=2 ############################## if [ "${OCF_RESKEY_other_networks}" != "false" ] ; then for network in ${OCF_RESKEY_other_networks} do ocf_log debug "Adding route on the host system to ${network}: ${OCF_RESKEY_namespace_ip}" ocf_run $RUN_IN_NS ip route replace ${network} via ${OCF_RESKEY_host_ip} metric 10000 done fi } haproxy_status() { get_variables # check and make PID file dir local PID_DIR="$( dirname ${PIDFILE} )" if [ ! -d "${PID_DIR}" ] ; then ocf_log debug "Create pid file dir: ${PID_DIR}" mkdir -p "${PID_DIR}" # no need to chown, root is user for haproxy chmod 755 "${PID_DIR}" fi if [ -n "${PIDFILE}" -a -f "${PIDFILE}" ]; then # haproxy is probably running # get pid from pidfile PID="`cat ${PIDFILE}`" if [ -n "${PID}" ]; then # check if process exists if $RUN ps -p "${PID}" | grep -q haproxy; then ocf_log info "haproxy daemon running" return $OCF_SUCCESS else ocf_log warn "haproxy daemon is not running but pid file exists" return $OCF_NOT_RUNNING fi else ocf_log err "PID file empty!" return $OCF_ERR_GENERIC fi fi # haproxy is not running ocf_log info "haproxy daemon is not running" return $OCF_NOT_RUNNING } haproxy_start() { get_variables # if haproxy is running return success haproxy_status retVal=$? if [ $retVal -eq $OCF_SUCCESS ]; then return $OCF_SUCCESS elif [ $retVal -ne $OCF_NOT_RUNNING ]; then ocf_log err "Error. Unknown status." return $OCF_ERR_GENERIC fi # run the haproxy binary ocf_run_as_root ${COMMAND} ${OCF_RESKEY_extraconf} -f "${CONF_FILE}" -p "${PIDFILE}" if [ $? -ne 0 ]; then ocf_log err "Error. haproxy daemon returned error $?." return $OCF_ERR_GENERIC fi if [ "${OCF_RESKEY_ns}" != '' ]; then set_ns_routing fi ocf_log info "Started haproxy daemon." return $OCF_SUCCESS } haproxy_reload() { local rc get_variables if haproxy_status; then # get pid from pidfile PID="`cat ${PIDFILE}`" # Safe-unblock the rules, if there are any unblock_client_access # Apply the blocking rule block_client_access rc=$? if [ $rc -eq $OCF_SUCCESS ]; then ocf_log info "Blocked all SYN for the Haproxy reload operation" else ocf_log warn "Cannot block all SYN for the Haproxy reload operation!" fi # reload haproxy binary replacing the old process ocf_run_as_root ${COMMAND} ${OCF_RESKEY_extraconf} -f "${CONF_FILE}" -p "${PIDFILE}" -sf "${PID}" rc=$? unblock_client_access ocf_log info "Unblocked all SYN for the Haproxy reload operation" if [ $rc -ne 0 ]; then ocf_log err "Error. haproxy daemon returned error $?." return $OCF_ERR_GENERIC fi else ocf_log info "Haproxy daemon is not running. Starting it." haproxy_start fi } haproxy_stop() { local rc local LH="${LL} haproxy_stop():" local shutdown_timeout=15 if [ -n "$OCF_RESKEY_CRM_meta_timeout" ]; then shutdown_timeout=$(( ($OCF_RESKEY_CRM_meta_timeout/1000) )) fi get_variables haproxy_status rc="${?}" if [ "${rc}" -eq "${OCF_NOT_RUNNING}" ]; then ocf_log info "${LH} haproxy already stopped." return "${OCF_SUCCESS}" fi proc_stop "${PIDFILE}" "${OCF_RESKEY_binpath}" $shutdown_timeout return "${?}" } haproxy_monitor() { haproxy_status } haproxy_validate_all() { get_variables if [ -n "$OCF_RESKEY_binpath" -a ! -x "$OCF_RESKEY_binpath" ]; then ocf_log err "Binary path $OCF_RESKEY_binpath does not exist." return $OCF_ERR_ARGS fi if [ -n "$OCF_RESKEY_conffile" -a ! -f "$OCF_RESKEY_conffile" ]; then ocf_log err "Config file $OCF_RESKEY_conffile does not exist." return $OCF_ERR_ARGS fi if grep -v "^#" "$CONF_FILE" | grep "pidfile" > /dev/null ; then : else ocf_log err "Error. \"pidfile\" entry required in the haproxy config file by haproxy OCF RA." return $OCF_ERR_GENERIC fi return $OCF_SUCCESS } haproxy_restart() { haproxy_stop haproxy_start } # # Main # if [ $# -ne 1 ]; then usage exit $OCF_ERR_ARGS fi umask 0022 export LL="${OCF_RESOURCE_INSTANCE}:" case $1 in start) haproxy_start ;; stop) haproxy_stop ;; reload) haproxy_reload ;; restart) haproxy_restart ;; status) haproxy_status ;; monitor) haproxy_monitor ;; validate-all) haproxy_validate_all ;; meta-data) meta_data ;; usage) usage; exit $OCF_SUCCESS ;; *) usage; exit $OCF_ERR_UNIMPLEMENTED ;; esac