#!/bin/bash
#
# lib/rpc_backend
# Interface for interactig with different RPC backends

# Dependencies:
#
# - ``functions`` file
# - ``RABBIT_{HOST|PASSWORD|USERID}`` must be defined when RabbitMQ is used
# - ``RPC_MESSAGING_PROTOCOL`` option for configuring the messaging protocol

# ``stack.sh`` calls the entry points in this order:
#
# - check_rpc_backend
# - install_rpc_backend
# - restart_rpc_backend
# - iniset_rpc_backend

# Save trace setting
XTRACE=$(set +o | grep xtrace)
set +o xtrace

RPC_MESSAGING_PROTOCOL=${RPC_MESSAGING_PROTOCOL:-0.9}

# TODO(sdague): RPC backend selection is super wonky because we treat
# messaging server as a service, which it really isn't for multi host
QPID_HOST=${QPID_HOST:-}


# Functions
# ---------

# Make sure we only have one rpc backend enabled.
# Also check the specified rpc backend is available on your platform.
function check_rpc_backend {
    local c svc

    local rpc_needed=1
    # We rely on the fact that filenames in lib/* match the service names
    # that can be passed as arguments to is_service_enabled.
    # We check for a call to iniset_rpc_backend in these files, meaning
    # the service needs a backend.
    rpc_candidates=$(grep -rl iniset_rpc_backend $TOP_DIR/lib/ | awk -F/ '{print $NF}')
    for c in ${rpc_candidates}; do
        if is_service_enabled $c; then
            rpc_needed=0
            break
        fi
    done
    local rpc_backend_cnt=0
    for svc in qpid zeromq rabbit; do
        is_service_enabled $svc &&
        (( rpc_backend_cnt++ )) || true
    done
    if [ "$rpc_backend_cnt" -gt 1 ]; then
        echo "ERROR: only one rpc backend may be enabled,"
        echo "       set only one of 'rabbit', 'qpid', 'zeromq'"
        echo "       via ENABLED_SERVICES."
    elif [ "$rpc_backend_cnt" == 0 ] && [ "$rpc_needed" == 0 ]; then
        echo "ERROR: at least one rpc backend must be enabled,"
        echo "       set one of 'rabbit', 'qpid', 'zeromq'"
        echo "       via ENABLED_SERVICES."
    fi

    if is_service_enabled qpid && ! qpid_is_supported; then
        die $LINENO "Qpid support is not available for this version of your distribution."
    fi
}

# clean up after rpc backend - eradicate all traces so changing backends
# produces a clean switch
function cleanup_rpc_backend {
    if is_service_enabled rabbit; then
        # Obliterate rabbitmq-server
        uninstall_package rabbitmq-server
        # in case it's not actually running, /bin/true at the end
        sudo killall epmd || sudo killall -9 epmd || /bin/true
        if is_ubuntu; then
            # And the Erlang runtime too
            apt_get purge -y erlang*
        fi
    elif is_service_enabled qpid; then
        if is_fedora; then
            uninstall_package qpid-cpp-server
        elif is_ubuntu; then
            uninstall_package qpidd
        else
            exit_distro_not_supported "qpid installation"
        fi
    elif is_service_enabled zeromq; then
        if is_fedora; then
            uninstall_package zeromq python-zmq
            if [ "$ZEROMQ_MATCHMAKER" == "redis" ]; then
                uninstall_package redis python-redis
            fi
        elif is_ubuntu; then
            uninstall_package libzmq1 python-zmq
            if [ "$ZEROMQ_MATCHMAKER" == "redis" ]; then
                uninstall_package redis-server python-redis
            fi
        elif is_suse; then
            uninstall_package libzmq1 python-pyzmq
            if [ "$ZEROMQ_MATCHMAKER" == "redis" ]; then
                uninstall_package redis python-redis
            fi
        else
            exit_distro_not_supported "zeromq installation"
        fi
    fi

    # Remove the AMQP 1.0 messaging libraries
    if [ "$RPC_MESSAGING_PROTOCOL" == "AMQP1" ]; then
        if is_fedora; then
            uninstall_package qpid-proton-c-devel
            uninstall_package python-qpid-proton
        fi
        # TODO(kgiusti) ubuntu cleanup
    fi
}

# install rpc backend
function install_rpc_backend {
    # Regardless of the broker used, if AMQP 1.0 is configured load
    # the necessary messaging client libraries for oslo.messaging
    if [ "$RPC_MESSAGING_PROTOCOL" == "AMQP1" ]; then
        if is_fedora; then
            install_package qpid-proton-c-devel
            install_package python-qpid-proton
        elif is_ubuntu; then
            # TODO(kgiusti) The QPID AMQP 1.0 protocol libraries
            # are not yet in the ubuntu repos. Enable these installs
            # once they are present:
            #install_package libqpid-proton2-dev
            #install_package python-qpid-proton
            # Also add 'uninstall' directives in cleanup_rpc_backend()!
            exit_distro_not_supported "QPID AMQP 1.0 Proton libraries"
        else
            exit_distro_not_supported "QPID AMQP 1.0 Proton libraries"
        fi
        # Install pyngus client API
        # TODO(kgiusti) can remove once python qpid bindings are
        # available on all supported platforms _and_ pyngus is added
        # to the requirements.txt file in oslo.messaging
        pip_install pyngus
    fi

    if is_service_enabled rabbit; then
        # Install rabbitmq-server
        install_package rabbitmq-server
    elif is_service_enabled qpid; then
        if is_fedora; then
            install_package qpid-cpp-server
        elif is_ubuntu; then
            install_package qpidd
        else
            exit_distro_not_supported "qpid installation"
        fi
        _configure_qpid
    elif is_service_enabled zeromq; then
        if is_fedora; then
            install_package zeromq python-zmq
            if [ "$ZEROMQ_MATCHMAKER" == "redis" ]; then
                install_package redis python-redis
            fi
        elif is_ubuntu; then
            install_package libzmq1 python-zmq
            if [ "$ZEROMQ_MATCHMAKER" == "redis" ]; then
                install_package redis-server python-redis
            fi
        elif is_suse; then
            install_package libzmq1 python-pyzmq
            if [ "$ZEROMQ_MATCHMAKER" == "redis" ]; then
                install_package redis python-redis
            fi
        else
            exit_distro_not_supported "zeromq installation"
        fi
        # Necessary directory for socket location.
        sudo mkdir -p /var/run/openstack
        sudo chown $STACK_USER /var/run/openstack
    fi

    # If using the QPID broker, install the QPID python client API
    if is_service_enabled qpid || [ -n "$QPID_HOST" ]; then
        install_package python-qpid
    fi
}

# restart the rpc backend
function restart_rpc_backend {
    if is_service_enabled rabbit; then
        # Start rabbitmq-server
        echo_summary "Starting RabbitMQ"
        # NOTE(bnemec): Retry initial rabbitmq configuration to deal with
        # the fact that sometimes it fails to start properly.
        # Reference: https://bugzilla.redhat.com/show_bug.cgi?id=1144100
        local i
        for i in `seq 10`; do
            local rc=0

            [[ $i -eq "10" ]] && die $LINENO "Failed to set rabbitmq password"

            if is_fedora || is_suse; then
                # service is not started by default
                restart_service rabbitmq-server
            fi

            rabbit_setuser "$RABBIT_USERID" "$RABBIT_PASSWORD" || rc=$?
            if [ $rc -ne 0 ]; then
                continue
            fi

            # change the rabbit password since the default is "guest"
            sudo rabbitmqctl change_password \
                $RABBIT_USERID $RABBIT_PASSWORD || rc=$?
            if [ $rc -ne 0 ]; then
                continue;
            fi

            break
        done
        if is_service_enabled n-cell; then
            # Add partitioned access for the child cell
            if [ -z `sudo rabbitmqctl list_vhosts | grep child_cell` ]; then
                sudo rabbitmqctl add_vhost child_cell
                sudo rabbitmqctl set_permissions -p child_cell $RABBIT_USERID ".*" ".*" ".*"
            fi
        fi
    elif is_service_enabled qpid; then
        echo_summary "Starting qpid"
        restart_service qpidd
    fi
}

# builds transport url string
function get_transport_url {
    if is_service_enabled qpid || [ -n "$QPID_HOST" ]; then
        echo "qpid://$QPID_USERNAME:$QPID_PASSWORD@$QPID_HOST:5672/"
    elif is_service_enabled rabbit || { [ -n "$RABBIT_HOST" ] && [ -n "$RABBIT_PASSWORD" ]; }; then
        echo "rabbit://$RABBIT_USERID:$RABBIT_PASSWORD@$RABBIT_HOST:5672/"
    fi
}

# iniset cofiguration
function iniset_rpc_backend {
    local package=$1
    local file=$2
    local section=${3:-DEFAULT}
    if is_service_enabled zeromq; then
        iniset $file $section rpc_backend "zmq"
        iniset $file $section rpc_zmq_host `hostname`
        if [ "$ZEROMQ_MATCHMAKER" == "redis" ]; then
            iniset $file $section rpc_zmq_matchmaker \
                oslo.messaging._drivers.matchmaker_redis.MatchMakerRedis
            MATCHMAKER_REDIS_HOST=${MATCHMAKER_REDIS_HOST:-127.0.0.1}
            iniset $file matchmaker_redis host $MATCHMAKER_REDIS_HOST
        else
            die $LINENO "Other matchmaker drivers not supported"
        fi
    elif is_service_enabled qpid || [ -n "$QPID_HOST" ]; then
        # For Qpid use the 'amqp' oslo.messaging transport when AMQP 1.0 is used
        if [ "$RPC_MESSAGING_PROTOCOL" == "AMQP1" ]; then
            iniset $file $section rpc_backend "amqp"
        else
            iniset $file $section rpc_backend "qpid"
        fi
        iniset $file $section qpid_hostname ${QPID_HOST:-$SERVICE_HOST}
        if [ -n "$QPID_USERNAME" ]; then
            iniset $file $section qpid_username $QPID_USERNAME
            iniset $file $section qpid_password $QPID_PASSWORD
        fi
    elif is_service_enabled rabbit || { [ -n "$RABBIT_HOST" ] && [ -n "$RABBIT_PASSWORD" ]; }; then
        iniset $file $section rpc_backend "rabbit"
        iniset $file oslo_messaging_rabbit rabbit_hosts $RABBIT_HOST
        iniset $file oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
        iniset $file oslo_messaging_rabbit rabbit_userid $RABBIT_USERID
    fi
}

# Check if qpid can be used on the current distro.
# qpid_is_supported
function qpid_is_supported {
    if [[ -z "$DISTRO" ]]; then
        GetDistro
    fi

    # Qpid is not in openSUSE
    ( ! is_suse )
}

function rabbit_setuser {
    local user="$1" pass="$2" found="" out=""
    out=$(sudo rabbitmqctl list_users) ||
        { echo "failed to list users" 1>&2; return 1; }
    found=$(echo "$out" | awk '$1 == user { print $1 }' "user=$user")
    if [ "$found" = "$user" ]; then
        sudo rabbitmqctl change_password "$user" "$pass" ||
            { echo "failed changing pass for '$user'" 1>&2; return 1; }
    else
        sudo rabbitmqctl add_user "$user" "$pass" ||
            { echo "failed changing pass for $user"; return 1; }
    fi
    sudo rabbitmqctl set_permissions "$user" ".*" ".*" ".*"
}

# Set up the various configuration files used by the qpidd broker
function _configure_qpid {

    # the location of the configuration files have changed since qpidd 0.14
    local qpid_conf_file
    if [ -e /etc/qpid/qpidd.conf ]; then
        qpid_conf_file=/etc/qpid/qpidd.conf
    elif [ -e /etc/qpidd.conf ]; then
        qpid_conf_file=/etc/qpidd.conf
    else
        exit_distro_not_supported "qpidd.conf file not found!"
    fi

    # force the ACL file to a known location
    local qpid_acl_file=/etc/qpid/qpidd.acl
    if [ ! -e $qpid_acl_file ]; then
        sudo mkdir -p -m 755 `dirname $qpid_acl_file`
        sudo touch $qpid_acl_file
        sudo chmod o+r $qpid_acl_file
    fi
    sudo sed -i.bak '/^acl-file=/d' $qpid_conf_file
    echo "acl-file=$qpid_acl_file" | sudo tee --append $qpid_conf_file

    sudo sed -i '/^auth=/d' $qpid_conf_file
    if [ -z "$QPID_USERNAME" ]; then
        # no QPID user configured, so disable authentication
        # and access control
        echo "auth=no" | sudo tee --append $qpid_conf_file
        cat <<EOF | sudo tee $qpid_acl_file
acl allow all all
EOF
    else
        # Configure qpidd to use PLAIN authentication, and add
        # QPID_USERNAME to the ACL:
        echo "auth=yes" | sudo tee --append $qpid_conf_file
        if [ -z "$QPID_PASSWORD" ]; then
            read_password QPID_PASSWORD "ENTER A PASSWORD FOR QPID USER $QPID_USERNAME"
        fi
        # Create ACL to allow $QPID_USERNAME full access
        cat <<EOF | sudo tee $qpid_acl_file
group admin ${QPID_USERNAME}@QPID
acl allow admin all
acl deny all all
EOF
        # Add user to SASL database
        if is_ubuntu; then
            install_package sasl2-bin
        elif is_fedora; then
            install_package cyrus-sasl-lib
            install_package cyrus-sasl-plain
        fi
        local sasl_conf_file=/etc/sasl2/qpidd.conf
        sudo sed -i.bak '/PLAIN/!s/mech_list: /mech_list: PLAIN /' $sasl_conf_file
        local sasl_db=`sudo grep sasldb_path $sasl_conf_file | cut -f 2 -d ":" | tr -d [:blank:]`
        if [ ! -e $sasl_db ]; then
            sudo mkdir -p -m 755 `dirname $sasl_db`
        fi
        echo $QPID_PASSWORD | sudo saslpasswd2 -c -p -f $sasl_db -u QPID $QPID_USERNAME
        sudo chmod o+r $sasl_db
    fi

    # If AMQP 1.0 is specified, ensure that the version of the
    # broker can support AMQP 1.0 and configure the queue and
    # topic address patterns used by oslo.messaging.
    if [ "$RPC_MESSAGING_PROTOCOL" == "AMQP1" ]; then
        QPIDD=$(type -p qpidd)
        if ! $QPIDD --help | grep -q "queue-patterns"; then
            exit_distro_not_supported "qpidd with AMQP 1.0 support"
        fi
        if ! grep -q "queue-patterns=exclusive" $qpid_conf_file; then
            cat <<EOF | sudo tee --append $qpid_conf_file
queue-patterns=exclusive
queue-patterns=unicast
topic-patterns=broadcast
EOF
        fi
    fi
}

# Restore xtrace
$XTRACE

# Tell emacs to use shell-script-mode
## Local variables:
## mode: shell-script
## End: