#!/bin/bash


# cleanup_opendaylight() - Remove residual data files, anything left over
# from previous runs that a clean run would need to clean up
function cleanup_opendaylight {
    # Wipe out the data, journal and snapshots directories ... grumble grumble grumble
    rm -rf $ODL_DIR/$ODL_NAME/{data,journal,snapshots}

    # Remove existing logfiles
    if [[ -n "$LOGDIR" ]]; then
        rm -f "$LOGDIR/$ODL_KARAF_LOG_BASE*"
    fi
    rm -f "$DEST/logs/$ODL_KARAF_LOG_BASE*"

    move_interface_addresses "outof_bridge"

    unbind_opendaylight_controller
}


# configure_opendaylight() - Set config files, create data dirs, etc
function configure_opendaylight {
    echo "Configuring OpenDaylight"

    # The logging config file in ODL
    local ODL_LOGGING_CONFIG=${ODL_DIR}/${ODL_NAME}/etc/org.ops4j.pax.logging.cfg

    # The feature config file in ODL for booting karaf features
    local ODL_KARAF_CONFIG=$ODL_DIR/$ODL_NAME/etc/org.apache.karaf.features.cfg

    # Add netvirt feature in Karaf, if it's not already there
    if ! (grep -w '^featuresBoot' $ODL_KARAF_CONFIG | grep $ODL_NETVIRT_KARAF_FEATURE); then
        # This format was introduced in oxygen
        sed -i "/^featuresBoot = / s/$/,$ODL_NETVIRT_KARAF_FEATURE/" \
            $ODL_KARAF_CONFIG
    fi

    # Move Jetty to $ODL_PORT
    if ! grep $ODL_PORT $ODL_DIR/$ODL_NAME/etc/jetty.xml; then
        # NOTE(yamahata): https://git.opendaylight.org/gerrit/#/c/51531/
        # removed 8080 port.
        if ! grep 808. $ODL_DIR/$ODL_NAME/etc/jetty.xml; then
            patch --input=$NETWORKING_ODL_DIR/devstack/jetty-legacy.patch $ODL_DIR/$ODL_NAME/etc/jetty.xml
        fi
        sed -i "/\<Property name\=\"jetty\.port/ s/808./$ODL_PORT/" \
            $ODL_DIR/$ODL_NAME/etc/jetty.xml
    fi

    # Configure conntrack for legacy netvirt
    if [[ "$ODL_LEGACY_NETVIRT_CONNTRACK" == "True" ]]; then
        NETVIRT_INIT_CONFIG_XML=$NETWORKING_ODL_DIR/devstack/odl-etc/opendaylight/datastore/initial/config/netvirt-impl-config_netvirt-impl-config.xml
        ODL_DATASTORE_INITIAL_CONFIG_DIR=${ODL_DIR}/${ODL_NAME}/etc/opendaylight/datastore/initial/config
        mkdir -p $ODL_DATASTORE_INITIAL_CONFIG_DIR
        cp --backup --force $NETVIRT_INIT_CONFIG_XML $ODL_DATASTORE_INITIAL_CONFIG_DIR/
    fi

    # Configure L3 if the user wants it for NETVIRT_OVSDB
    # L3 is always enabled in NETVIRT_VPNSERVICE
    if [[ ",$ODL_NETVIRT_KARAF_FEATURE," =~ ",$ODL_NETVIRT_KARAF_FEATURE_OVSDB," ]] && [ "${ODL_L3}" == "True" ]; then
        # Configure L3 FWD if it's not there
        if ! grep ^ovsdb.l3.fwd.enabled $ODL_DIR/$ODL_NAME/etc/custom.properties; then
            echo "ovsdb.l3.fwd.enabled=yes" >> $ODL_DIR/$ODL_NAME/etc/custom.properties
        fi

        # Configure L3 GW MAC if it's not there
        if ! grep ^ovsdb.l3gateway.mac $ODL_DIR/$ODL_NAME/etc/custom.properties && [[ -n "$ODL_L3GW_MAC" ]]; then
            echo "ovsdb.l3gateway.mac=$ODL_L3GW_MAC" >> $ODL_DIR/$ODL_NAME/etc/custom.properties
        fi
    fi

    # create symbolic link from ODL etc, configuration dir under /etc/networking-odl
    # so that those config files are copied to log server
    local NETWORKING_ODL_ETC_DIR=/etc/networking-odl
    local ODL_CONF_DIR=$NETWORKING_ODL_ETC_DIR/odl
    sudo mkdir -p $ODL_CONF_DIR
    local d
    for d in etc configuration; do
        sudo ln -sf "$ODL_DIR/$ODL_NAME/$d" "$ODL_CONF_DIR/"
    done

    # NOTE(yamahata): by default LOGDIR=$DEST via ${LOG_FILE%/*}
    # default value of LOGFILE by devstack-vm-gate.sh: $BASE/new/devstacklog.txt
    # default value of LOG_DIR by devstack/stackrc: $BASE/new
    # however, cleanup_host() in devstack-gate/functions.sh
    # doesn't copy files under $BASE/new, but $BASE/{old, new}/logs
    # try to $BASE/logs
    local ODL_LOGDIR
    if [[ -n "$LOGDIR" ]]; then
        if [[ "$LOGDIR" != "$DEST" ]]; then
            ODL_LOGDIR=$LOGDIR
        else
            ODL_LOGDIR=${LOGDIR%/*}/logs
        fi
    else
        ODL_LOGDIR=${DEST%/*}/logs
    fi

    # Remove existing logfiles
    rm -f "$ODL_LOGDIR/$ODL_KARAF_LOG_BASE*"
    # Log karaf output to a file
    _LF=$ODL_LOGDIR/$ODL_KARAF_LOG_NAME
    LF=$(echo $_LF | sed 's/\//\\\//g')
    # Soft link for easy consumption
    sudo mkdir -p "$ODL_LOGDIR"
    sudo chown $(id -un):$(id -gn) "$ODL_LOGDIR"
    sudo ln -sf $_LF "$ODL_LOGDIR/screen-karaf.log"

    # This format was introduced in oxygen
    sed -i -e "/^log4j2\.appender\.rolling\.fileName/ s/.*/log4j2\.appender\.rolling\.fileName\ = $LF/" \
        -e "/^log4j2\.appender\.rolling\.policies\.size\.size/ s/.*/log4j2\.appender\.rolling\.policies\.size\.size\ = 1024GB/" \
        $ODL_DIR/$ODL_NAME/etc/org.ops4j.pax.logging.cfg

    # Configure DEBUG logs for network virtualization in odl, if the user wants it
    if [ "${ODL_NETVIRT_DEBUG_LOGS}" == "True" ]; then
        if ! grep ^log4j.logger.org.opendaylight.ovsdb $ODL_LOGGING_CONFIG; then
            echo 'log4j.logger.org.opendaylight.ovsdb = INFO, out' >> $ODL_LOGGING_CONFIG
        fi
        if ! grep ^log4j.logger.org.opendaylight.netvirt $ODL_LOGGING_CONFIG; then
            echo 'log4j.logger.org.opendaylight.netvirt = DEBUG, out' >> $ODL_LOGGING_CONFIG
        fi
        if ! grep ^log4j.logger.org.opendaylight.neutron $ODL_LOGGING_CONFIG; then
            echo 'log4j.logger.org.opendaylight.neutron = DEBUG, out' >> $ODL_LOGGING_CONFIG
        fi
    fi
}

# configure_neutron_opendaylight() - Set Neutron config files according to ODL settings
function configure_neutron_odl {
    echo "Configuring ML2 for OpenDaylight"

    # NOTE(mpeterson): Create the state_path that will be used by neutron
    # since although it expects it to exist, it is not created at any time
    # and therefore when we first want to use it in the driver it fails.
    # refer to: https://github.com/openstack-dev/devstack/blob/d37119e797d3140aeb0038a1129ce5e9016c1a36/lib/neutron#L46
    # and: https://github.com/openstack-dev/devstack/blob/d37119e797d3140aeb0038a1129ce5e9016c1a36/lib/neutron-legacy#L698
    sudo mkdir -p $DATA_DIR/neutron
    safe_chown -R $STACK_USER $DATA_DIR
    safe_chmod 0755 $DATA_DIR/neutron

    # https://bugs.launchpad.net/neutron/+bug/1614766
    # Allow ovsdb_interface native by avoiding port conflict.
    if [[ -n "$ODL_OVSDB_ALTPORT" ]]; then
        iniset $NEUTRON_CONF OVS ovsdb_connection tcp:127.0.0.1:$ODL_OVSDB_ALTPORT
        iniset $NEUTRON_DHCP_CONF OVS ovsdb_connection tcp:127.0.0.1:$ODL_OVSDB_ALTPORT
    fi

    # Addition of L3 service_plugin
    if ! is_neutron_legacy_enabled; then
        neutron_service_plugin_class_add odl-router_v2
        # NOTE: workaround, mechanism driver is not being set to opendaylight_v2
        # by lib/neutron. It seems to be hardcoded at
        # https://github.com/openstack-dev/devstack/blob/master/lib/neutron#L184, fix it, if
        # fixed in lib/neutron
        iniset $NEUTRON_CORE_PLUGIN_CONF ml2 mechanism_drivers $Q_ML2_PLUGIN_MECHANISM_DRIVERS
    fi
    populate_odl_ml2_config ml2_odl url $ODL_ENDPOINT
    populate_odl_ml2_config ml2_odl username $ODL_USERNAME
    populate_odl_ml2_config ml2_odl password $ODL_PASSWORD
    populate_odl_ml2_config ml2_odl port_binding_controller $ODL_PORT_BINDING_CONTROLLER
    populate_odl_ml2_config ml2_odl enable_dhcp_service $ODL_DHCP_SERVICE

    if [[ -n "$ODL_TIMEOUT" ]]; then
        populate_odl_ml2_config ml2_odl timeout $ODL_TIMEOUT
    fi
    # When it's not set, the default value is set by networking-odl
    if [[ -n "$ODL_HOSTCONF_URI" ]]; then
        populate_odl_ml2_config ml2_odl odl_hostconf_uri $ODL_HOSTCONF_URI
    fi

    # NOTE(mgkwill): ODL layer-3 and DHCP services currently lack support
    # for metadata. Enabling both native services also requires enabling
    # config drive to provide instances with metadata. If conventional DHCP agent
    # is used instead, configure it to provide instances with metadata.
    # TODO(rajivk) Remove q-dhcp on adoption of lib/neutron
    if is_service_enabled neutron-dhcp; then
        # Conventional DHCP agent must provide all metadata when ODL
        # layer-3 is enabled. The conventional DHCP agent will be forced
        # to provide metadata for all networks.
        iniset $NEUTRON_DHCP_CONF DEFAULT force_metadata True
    elif is_service_enabled q-dhcp; then
        iniset $Q_DHCP_CONF_FILE DEFAULT force_metadata True
    fi

    if [[ "$ODL_L3" == "True" ]]; then
        if is_service_enabled n-cpu; then
            iniset $NOVA_CONF DEFAULT force_config_drive True
        fi
    fi
}

function configure_neutron_odl_lightweight_testing {
    echo "Configuring lightweight testing for OpenDaylight"
    if is_service_enabled q-dhcp neutron-dhcp; then
        populate_odl_ml2_config ml2_odl enable_lightweight_testing True
    fi
}

# init_opendaylight() - Initialize databases, etc.
function init_opendaylight {
    # clean up from previous (possibly aborted) runs
    # create required data files
    :
}


# install_opendaylight() - Collect source and prepare
function install_opendaylight {
    if [[ "$ODL_INSTALL" == "False" ]]; then
        return
    fi

    echo "Installing OpenDaylight and dependent packages"
    if [[ "$ODL_USING_EXISTING_JAVA" != "True" ]]; then
        if ! setup_java "${ODL_REQUIRED_JAVA_VERSION:-7}"; then
            exit 1
        fi
    fi

    # Download OpenDaylight
    cd $ODL_DIR

    if [[ "$OFFLINE" != "True" ]]; then
        wget -N $ODL_URL/$ODL_PKG
    fi
    unzip -u -o $ODL_PKG
}


# install_networking_odl() - Install the ML2 driver and other plugins/drivers
function install_networking_odl {
    echo "Installing the Networking-ODL driver for OpenDaylight"
    setup_develop $NETWORKING_ODL_DIR
}


# install_opendaylight_compute() - Make sure OVS is installed
function install_opendaylight_compute {
    if [[ "$SKIP_OVS_INSTALL" = "True" ]]; then
        echo "Skipping OVS installation."
    else
        # packages are the same as for Neutron OVS agent
        _neutron_ovs_base_install_agent_packages
    fi
}


# start_opendaylight() - Start running processes, including screen
function start_opendaylight {
    echo "Starting OpenDaylight"

    # Wipe out the data and journal directories ... grumble grumble grumble
    rm -rf $ODL_DIR/$ODL_NAME/{data,journal}

    # There variables needed by the running karaf process are set in the
    # function setup_java_env. See the "bin/setenv" file in the OpenDaylight
    # distribution for their individual meaning.
    setup_java_env

    # Extra configuration variables that may be used if required.
    if [[ -n "$JAVA_MIN_MEM" ]]; then
        export JAVA_MIN_MEM=$ODL_JAVA_MIN_MEM
    fi

    if [[ -n "$JAVA_MAX_MEM" ]]; then
        export JAVA_MAX_MEM=$ODL_JAVA_MAX_MEM
    fi

    if [[ -n "$JAVA_MAX_PERM_MEM" ]]; then
        export JAVA_MAX_PERM_MEM=$ODL_JAVA_MAX_PERM_MEM
    fi

    # this is a forking process, just start it in the background
    $ODL_DIR/$ODL_NAME/bin/start

    if [ -n "$ODL_BOOT_WAIT_URL" ]; then
        echo "Waiting for OpenDaylight to start via $ODL_BOOT_WAIT_URL ..."
        # Probe ODL restconf for netvirt until it is operational
        local testcmd="curl -o /dev/null --fail --silent --head -u \
              ${ODL_USERNAME}:${ODL_PASSWORD} http://${ODL_MGR_HOST}:${ODL_PORT}/${ODL_BOOT_WAIT_URL}"
        test_with_retry "$testcmd" "OpenDaylight did not start after $ODL_BOOT_WAIT" \
                        $ODL_BOOT_WAIT $ODL_RETRY_SLEEP_INTERVAL
    else
        echo "Waiting for OpenDaylight to start ..."
        # Sleep a bit to let OpenDaylight finish starting up
        sleep $ODL_BOOT_WAIT
    fi
}


# stop_opendaylight() - Stop running processes (non-screen)
function stop_opendaylight {
    # Stop the karaf container
    $ODL_DIR/$ODL_NAME/bin/stop
}


# cleanup_opendaylight_compute() - Remove all OVS ports, bridges and disconnects
# controller from switch
function cleanup_opendaylight_compute {
    # Remove the patch ports
    for port in $(sudo ovs-vsctl show | grep Port | awk '{print $2}'  | cut -d '"' -f 2 | grep patch); do
        sudo ovs-vsctl del-port ${port}
    done

    # remove all OVS ports that look like Neutron created ports
    for port in $(sudo ovs-vsctl list port | grep -o -e tap[0-9a-f\-]* -e q[rg]-[0-9a-f\-]*); do
        sudo ovs-vsctl del-port ${port}
    done

    # Remove all the vxlan ports
    for port in $(sudo ovs-vsctl list port | grep name | grep vxlan | awk '{print $3}'  | cut -d '"' -f 2); do
        sudo ovs-vsctl del-port ${port}
    done

    # Disconnect controller from switch
    unbind_opendaylight_controller

    # remove all OVS bridges created by ODL
    for bridge in $(sudo ovs-vsctl list-br | grep -o -e ${OVS_BR} -e ${PUBLIC_BRIDGE}); do
        sudo ovs-vsctl del-br ${bridge}
    done
}

# bind_opendaylight_controller() - set control manager to OVS
function bind_opendaylight_controller {
    echo_summary "Initializing OpenDaylight"
    ODL_LOCAL_IP=${ODL_LOCAL_IP:-$HOST_IP}
    ODL_MGR_PORT=${ODL_MGR_PORT:-6640}
    read ovstbl <<< $(sudo ovs-vsctl get Open_vSwitch . _uuid)

    # NOTE(yamahata): setup ovsdb configuration first before setting
    # ovsdb manager not to show transitional state
    if [[ -n "$ODL_PROVIDER_MAPPINGS" ]]; then
        sudo ovs-vsctl set Open_vSwitch $ovstbl \
            other_config:provider_mappings=$ODL_PROVIDER_MAPPINGS
    fi
    sudo ovs-vsctl set Open_vSwitch $ovstbl other_config:local_ip=$ODL_LOCAL_IP
    if [[ ",$ODL_NETVIRT_KARAF_FEATURE," =~ ",odl-netvirt-sfc," ]]; then
        sudo ovs-vsctl set Open_vSwitch $ovstbl external_ids:of-tunnel=true
    fi
    # for pseudo agent port binding
    if [ "$ODL_PORT_BINDING_CONTROLLER" == "pseudo-agentdb-binding" ]; then
        ODL_OVS_HOSTCONFIGS_OPTIONS=${ODL_OVS_HOSTCONFIGS_OPTIONS:---debug --noovs_dpdk}
        if [[ -n "$ODL_PROVIDER_MAPPINGS" ]]; then
            ODL_OVS_HOSTCONFIGS_OPTIONS="${ODL_OVS_HOSTCONFIGS_OPTIONS} --bridge_mappings=${ODL_PROVIDER_MAPPINGS}"
        fi
        if [[ -n "$ODL_OVS_HOSTCONFIGS" ]]; then
            ODL_OVS_HOSTCONFIGS_OPTIONS=${ODL_OVS_HOSTCONFIGS_OPTIONS} --ovs_hostconfigs="$ODL_OVS_HOSTCONFIGS"
        fi
        if [[ ! -f $NEUTRON_CONF ]]; then
            sudo neutron-odl-ovs-hostconfig  $ODL_OVS_HOSTCONFIGS_OPTIONS
        else
            sudo neutron-odl-ovs-hostconfig --config-file=$NEUTRON_CONF $ODL_OVS_HOSTCONFIGS_OPTIONS
        fi
    fi

    if [[ -n "$PUBLIC_BRIDGE" ]]; then
        sudo ovs-vsctl --no-wait -- --may-exist add-br $PUBLIC_BRIDGE
    fi

    # Lastly setup ovsdb manager
    local ODL_MANAGERS_PARAM=()
    for manager in $(echo $ODL_OVS_MANAGERS | tr "," "\n"); do
        local manager_ip
        manager_ip=$(gethostip -d ${manager})
        ODL_MANAGERS_PARAM=( "${ODL_MANAGERS_PARAM[@]}" "tcp:${manager_ip}:$ODL_MGR_PORT" )
    done
    # don't overwrite the already existing managers
    local ODL_MANAGERS_OLD
    ODL_MANAGERS_OLD=$(sudo ovs-vsctl get-manager)
    local ODL_MANAGERS
    ODL_MANAGERS=$(echo $ODL_MANAGERS_OLD ${ODL_MANAGERS_PARAM[@]} | tr ' ' '\n' | sort | uniq | tr '\n' ' ')
    sudo ovs-vsctl set-manager ${ODL_MANAGERS}
}

# unbind_opendaylight_controller() - disconnect controller from switch and clear bridges
function unbind_opendaylight_controller {
    sudo ovs-vsctl del-manager
    BRIDGES=$(sudo ovs-vsctl list-br)
    for bridge in $BRIDGES ; do
        sudo ovs-vsctl del-controller $bridge
    done
}


function _configure_veth {
    ip link show $Q_PUBLIC_VETH_INT > /dev/null 2>&1 ||
        sudo ip link add $Q_PUBLIC_VETH_INT type veth \
             peer name $Q_PUBLIC_VETH_EX
    sudo ip link set $Q_PUBLIC_VETH_INT up
    sudo ip link set $Q_PUBLIC_VETH_EX up
    sudo ip addr flush dev $Q_PUBLIC_VETH_EX
    if [[ ",$ODL_NETVIRT_KARAF_FEATURE," =~ ",$ODL_NETVIRT_KARAF_FEATURE_OVSDB," ]]; then
        local OVSBR_EX
        OVSBR_EX=$(echo $ODL_PROVIDER_MAPPINGS | cut -d ':' -f1)
        sudo ovs-vsctl --may-exist add-port $OVSBR_EX $Q_PUBLIC_VETH_INT
    else
        sudo ovs-vsctl --may-exist add-port $OVS_BR $Q_PUBLIC_VETH_INT
    fi

    local cidr_len=${FLOATING_RANGE#*/}
    sudo ip addr replace ${PUBLIC_NETWORK_GATEWAY}/$cidr_len dev $Q_PUBLIC_VETH_EX
    sudo ip route replace $FLOATING_RANGE dev $Q_PUBLIC_VETH_EX
    if [[ -n "$IPV6_PUBLIC_RANGE" ]] && [[ -n "$IPV6_PUBLIC_NETWORK_GATEWAY" ]] && [[ -n "$FIXED_RANGE_V6" ]] && [[ -n "$IPV6_ROUTER_GW_IP" ]]; then
        local ipv6_cidr_len=${IPV6_PUBLIC_RANGE#*/}
        sudo ip -6 addr replace ${IPV6_PUBLIC_NETWORK_GATEWAY}/$ipv6_cidr_len dev ${Q_PUBLIC_VETH_EX}
        sudo ip -6 route replace $IPV6_PUBLIC_RANGE dev $Q_PUBLIC_VETH_EX
    fi
}

function _configure_opendaylight_l3_legacy_netvirt {
    wait_for_active_bridge $PUBLIC_BRIDGE $ODL_RETRY_SLEEP_INTERVAL $ODL_BOOT_WAIT

    if [[ "$Q_USE_PUBLIC_VETH" == "True" ]]; then
        _configure_veth
    fi
}

function _configure_opendaylight_l3_new_netvirt {
    if [[ "$Q_USE_PUBLIC_VETH" == "True" ]]; then
        _configure_veth
    fi
}


# configure_opendaylight_l3() - configure bridges for OpenDaylight L3 forwarding
function configure_opendaylight_l3 {
    if [[ ",$ODL_NETVIRT_KARAF_FEATURE," =~ ",$ODL_NETVIRT_KARAF_FEATURE_OVSDB," ]]; then
        _configure_opendaylight_l3_legacy_netvirt
    else
        _configure_opendaylight_l3_new_netvirt
    fi
}
