#!/bin/bash
#
# Copyright 2016 Citrix Systems
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
#

MODE=$1
PHASE=$2

OS_XENAPI_DIR=$DEST/os-xenapi
XS_DOM0_IPTABLES_CHAIN="XenServerDevstack"

DOM0_OVSDB_PORT=${DOM0_OVSDB_PORT:-"6640"}
DOM0_VXLAN_PORT=${DOM0_VXLAN_PORT:-"4789"}


function get_dom0_ssh {
    local dom0_ip
    dom0_ip=$(echo "$XENAPI_CONNECTION_URL" | cut -d "/" -f 3)

    local ssh_dom0
    ssh_dom0="sudo -u $DOMZERO_USER ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$dom0_ip"
    echo $ssh_dom0
    return 0
}

# Install Nova and Neutron Dom0 plugins
function install_dom0_plugins {
    local ssh_dom0
    ssh_dom0=$(get_dom0_ssh)

    local dom0_func
    dom0_func=`cat $OS_XENAPI_DIR/devstack/dom0_functions`
    local dom0_plugin_dir
    dom0_plugin_dir=`$ssh_dom0 "$dom0_func; set -eux; dom0_plugin_location"`

    # We've moved the plugins from neutron/nova to os-xenapi, but in some stable branches the
    # plugins are still located in neutron (ocata and backforward branches) or nova (Newton
    # and backforward branches). In order to upport both stable and master branches, let's
    # check the existing for the potential plugin directories. And copy them if exist.
    local plugin_dir
    local need_install_xenapi=False
    # for neutron plugins
    plugin_dir=$DEST/neutron/neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/etc/xapi.d/plugins/
    if [ -d $plugin_dir ]; then
        need_install_xenapi=True
        tar -czf - -C $plugin_dir ./ | $ssh_dom0 "tar -xzf - -C $dom0_plugin_dir"
    fi
    # for nova plugins
    plugin_dir=$DEST/nova/plugins/xenserver/xenapi/etc/xapi.d/plugins/
    if [ -d $plugin_dir ]; then
        need_install_xenapi=True
        tar -czf - -C $plugin_dir ./ | $ssh_dom0 "tar -xzf - -C $dom0_plugin_dir"
    fi

    if [ "$need_install_xenapi" = "True" ]; then
        # Either neutron or nova need XenAPI, install XenAPI.
        pip_install_gr xenapi
    fi

    # Get the path that os-xenapi is installed or the path that nova source code resides
    os_xenapi_dir=$(sudo -H pip show os-xenapi |grep "Location:"|cut -d " " -f 2-)
    if [ -n "$os_xenapi_dir" ]; then
        plugin_dir=$os_xenapi_dir/os_xenapi/dom0/etc/xapi.d/plugins/
        if [ -d $plugin_dir ]; then
            tar -czf - -C $plugin_dir ./ | $ssh_dom0 "tar -xzf - -C $dom0_plugin_dir"
        fi
    fi
    # change plugins to be executable
    $ssh_dom0 "chmod a+x $dom0_plugin_dir/*"
}

# Config iptables in Dom0
function config_dom0_iptables {
    local ssh_dom0=$(get_dom0_ssh)

    # Remove restriction on linux bridge in Dom0 so security groups
    # can be applied to the interim bridge-based network.
    $ssh_dom0 "rm -f /etc/modprobe.d/blacklist-bridge*"

    # Save errexit setting
    _ERREXIT_XENSERVER=$(set +o | grep errexit)
    set +o errexit

    # Check Dom0 internal chain for Neutron, add if not exist
    $ssh_dom0 "iptables -t filter -L $XS_DOM0_IPTABLES_CHAIN"
    local chain_result=$?
    if [ "$chain_result" != "0" ]; then
        $ssh_dom0 "iptables -t filter --new $XS_DOM0_IPTABLES_CHAIN"
        $ssh_dom0 "iptables -t filter -I INPUT -j $XS_DOM0_IPTABLES_CHAIN"
    fi

    # Check iptables for remote ovsdb connection, add if not exist
    $ssh_dom0 "iptables -t filter -C $XS_DOM0_IPTABLES_CHAIN -p tcp -m tcp --dport $DOM0_OVSDB_PORT -j ACCEPT"
    local remote_conn_result=$?
    if [ "$remote_conn_result" != "0" ]; then
        $ssh_dom0 "iptables -t filter -I $XS_DOM0_IPTABLES_CHAIN -p tcp --dport $DOM0_OVSDB_PORT -j ACCEPT"
    fi

    # Check iptables for VxLAN, add if not exist
    $ssh_dom0 "iptables -t filter -C $XS_DOM0_IPTABLES_CHAIN -p udp -m multiport --dports $DOM0_VXLAN_PORT -j ACCEPT"
    local vxlan_result=$?
    if [ "$vxlan_result" != "0" ]; then
        $ssh_dom0 "iptables -t filter -I $XS_DOM0_IPTABLES_CHAIN -p udp -m multiport --dport $DOM0_VXLAN_PORT -j ACCEPT"
    fi

    # Restore errexit setting
    $_ERREXIT_XENSERVER
}

# Configure ovs agent for compute node, i.e. q-domua
function config_ovs_agent {
    # TODO(huan): remove below line when https://review.openstack.org/#/c/435224/ merged
    sudo rm -f $NEUTRON_CORE_PLUGIN_CONF.domU

    # Make a copy of our config for domU
    sudo cp $NEUTRON_CORE_PLUGIN_CONF $NEUTRON_CORE_PLUGIN_CONF.domU

    # Change domU's config file to STACK_USER
    sudo chown $STACK_USER:$STACK_USER $NEUTRON_CORE_PLUGIN_CONF.domU

    # Configure xen configuration for neutron rootwrap.conf
    iniset $NEUTRON_ROOTWRAP_CONF_FILE xenapi xenapi_connection_url "$XENAPI_CONNECTION_URL"
    iniset $NEUTRON_ROOTWRAP_CONF_FILE xenapi xenapi_connection_username "$XENAPI_USER"
    iniset $NEUTRON_ROOTWRAP_CONF_FILE xenapi xenapi_connection_password "$XENAPI_PASSWORD"

    # Configure q-domua, use Dom0's hostname and concat suffix
    local ssh_dom0=$(get_dom0_ssh)
    local dom0_hostname=`$ssh_dom0 "hostname"`
    iniset $NEUTRON_CORE_PLUGIN_CONF.domU DEFAULT host "${dom0_hostname}"

    # Configure xenapi for q-domua to use its xenserver rootwrap daemon
    iniset $NEUTRON_CORE_PLUGIN_CONF.domU xenapi connection_url "$XENAPI_CONNECTION_URL"
    iniset $NEUTRON_CORE_PLUGIN_CONF.domU xenapi connection_username "$XENAPI_USER"
    iniset $NEUTRON_CORE_PLUGIN_CONF.domU xenapi connection_password "$XENAPI_PASSWORD"
    iniset $NEUTRON_CORE_PLUGIN_CONF.domU agent root_helper ""
    iniset $NEUTRON_CORE_PLUGIN_CONF.domU agent root_helper_daemon "xenapi_root_helper"

    # Set integration bridge for ovs-agent in compute node (q-domua)
    iniset $NEUTRON_CORE_PLUGIN_CONF.domU ovs integration_bridge $OVS_BRIDGE

    # Set OVS native interface for ovs-agent in compute node (q-domua)
    local dom0_ip=$(echo "$XENAPI_CONNECTION_URL" | cut -d "/" -f 3)
    iniset $NEUTRON_CORE_PLUGIN_CONF.domU ovs ovsdb_connection tcp:$dom0_ip:$DOM0_OVSDB_PORT
    iniset $NEUTRON_CORE_PLUGIN_CONF.domU ovs of_listen_address $HOST_IP

    if [[ "$ENABLE_TENANT_VLANS" == "True" ]]; then
        # Create a bridge "br-$VLAN_INTERFACE" and add port
        _neutron_ovs_base_add_bridge "br-$VLAN_INTERFACE"
        sudo ovs-vsctl -- --may-exist add-port "br-$VLAN_INTERFACE" $VLAN_INTERFACE

        # Set bridge mapping for q-domua which is for compute node
        iniset $NEUTRON_CORE_PLUGIN_CONF.domU ovs bridge_mappings "physnet1:$FLAT_NETWORK_BRIDGE"

        # Set bridge mappings for q-agt as we have an extra bridge mapping physnet1 for domU and dom0
        iniset $NEUTRON_CORE_PLUGIN_CONF ovs bridge_mappings "physnet1:br-$VLAN_INTERFACE,$PHYSICAL_NETWORK:$OVS_PHYSICAL_BRIDGE"

    elif [[ "$OVS_ENABLE_TUNNELING" == "True" ]]; then
        # Set tunnel ip for openvswitch agent in compute node (q-domua).
        # All q-domua's OVS commands are executed in Dom0, so the tunnel
        # is established between Dom0 and DomU(where DevStack runs), and
        # we need to set local_ip in q-domua that is used for Dom0
        iniset $NEUTRON_CORE_PLUGIN_CONF.domU ovs bridge_mappings ""
        iniset $NEUTRON_CORE_PLUGIN_CONF.domU ovs local_ip $dom0_ip
        iniset $NEUTRON_CORE_PLUGIN_CONF.domU ovs tunnel_bridge $OVS_TUNNEL_BRIDGE
    fi
}

function config_nova_compute {
    iniset $NOVA_CONF xenserver vif_driver nova.virt.xenapi.vif.XenAPIOpenVswitchDriver
    iniset $NOVA_CONF xenserver ovs_integration_bridge $OVS_BRIDGE
    iniset $NOVA_CONF DEFAULT firewall_driver nova.virt.firewall.NoopFirewallDriver
    # Configure nova-compute, use Dom0's hostname and concat suffix
    local ssh_dom0=$(get_dom0_ssh)
    local dom0_hostname=`$ssh_dom0 "hostname"`
    iniset $NOVA_CONF DEFAULT host "${dom0_hostname}"
}

function config_ceilometer {
    if is_service_enabled ceilometer-acompute; then
        local ssh_dom0=$(get_dom0_ssh)
        local dom0_hostname=`$ssh_dom0 "hostname"`
        iniset $CEILOMETER_CONF DEFAULT host "${dom0_hostname}"
        iniset $CEILOMETER_CONF DEFAULT hypervisor_inspector xenapi

        iniset $CEILOMETER_CONF xenapi connection_url "$XENAPI_CONNECTION_URL"
        iniset $CEILOMETER_CONF xenapi connection_username "$XENAPI_USER"
        iniset $CEILOMETER_CONF xenapi connection_password "$XENAPI_PASSWORD"

        # For XenAPI driver, we cannot use default value "libvirt_metadata"
        # https://github.com/openstack/ceilometer/blob/master/ceilometer/compute/discovery.py#L125
        iniset $CEILOMETER_CONF compute instance_discovery_method naive
    fi
}

# Start neutron-openvswitch-agent for Dom0 (q-domua)
function start_ovs_agent {
    local config_file="--config-file $NEUTRON_CONF --config-file $NEUTRON_CORE_PLUGIN_CONF.domU"

    # TODO(huanxie): neutron-legacy is deprecated, checking is_neutron_legacy_enabled
    # can make our code more compatible with devstack future changes, see link
    # https://github.com/openstack-dev/devstack/blob/master/lib/neutron-legacy#L62
    if is_neutron_legacy_enabled; then
        # TODO(huanxie): delete below when https://review.openstack.org/#/c/435224/ merged
        stop_process q-domua

        run_process q-domua "$AGENT_BINARY $config_file"
    else
        run_process neutron-agent-dom0 "$NEUTRON_BIN_DIR/$NEUTRON_AGENT_BINARY $config_file"
    fi
}

# Stop neutron-openvswitch-agent for Dom0 (q-domua)
function stop_ovs_agent {
    if is_neutron_legacy_enabled; then
        stop_process q-domua
    else
        stop_process neutron-agent-dom0
    fi
}

function start_ceilometer_acompute {
    if is_service_enabled ceilometer-acompute; then
        run_process ceilometer-acompute "$CEILOMETER_BIN_DIR/ceilometer-polling --polling-namespaces compute --config-file $CEILOMETER_CONF"
    fi
}

# Remove Dom0 firewall rules created by this plugin
function cleanup_dom0_iptables {
    local ssh_dom0=$(get_dom0_ssh)

    # Save errexit setting
    _ERREXIT_XENSERVER=$(set +o | grep errexit)
    set +o errexit

    $ssh_dom0 "iptables -t filter -L $XS_DOM0_IPTABLES_CHAIN"
    local chain_result=$?
    if [ "$chain_result" == "0" ]; then
        $ssh_dom0 "iptables -t filter -F $XS_DOM0_IPTABLES_CHAIN"
        $ssh_dom0 "iptables -t filter -D INPUT -j $XS_DOM0_IPTABLES_CHAIN"
        $ssh_dom0 "iptables -t filter -X $XS_DOM0_IPTABLES_CHAIN"
    fi

    # Restore errexit setting
    $_ERREXIT_XENSERVER
}

# Prepare directories for kernels and images in Dom0
function create_dom0_kernel_and_image_dir {
    local ssh_dom0=$(get_dom0_ssh)

    {
        echo "set -eux"
        cat $OS_XENAPI_DIR/devstack/dom0_functions
        echo "create_directory_for_images"
        echo "create_directory_for_kernels"
    } | $ssh_dom0
}

# Install conntrack-tools in Dom0
function install_dom0_conntrack {
    local ssh_dom0=$(get_dom0_ssh)

    {
        echo "set -eux"
        cat $OS_XENAPI_DIR/devstack/dom0_functions
        echo "install_conntrack_tools"
    } | $ssh_dom0
}

if [[ "$MODE" == "stack" ]]; then
    case "$PHASE" in
        pre-install)
            # Called after system (OS) setup is complete and before project source is installed
            ;;
        install)
            # Called after the layer 1 and 2 projects source and their dependencies have been installed
            install_dom0_plugins
            config_dom0_iptables
            install_dom0_conntrack
            create_dom0_kernel_and_image_dir
            # set image variables
            DEFAULT_IMAGE_NAME="cirros-${CIRROS_VERSION}-${CIRROS_ARCH}-disk"
            DEFAULT_IMAGE_FILE_NAME="cirros-${CIRROS_VERSION}-${CIRROS_ARCH}-disk.vhd.tgz"
            IMAGE_URLS="http://ca.downloads.xensource.com/OpenStack/cirros-${CIRROS_VERSION}-${CIRROS_ARCH}-disk.vhd.tgz"
            IMAGE_URLS+=",http://download.cirros-cloud.net/${CIRROS_VERSION}/cirros-${CIRROS_VERSION}-x86_64-uec.tar.gz"
            ;;
        post-config)
            # Called after the layer 1 and 2 services have been configured.
            # All configuration files for enabled services should exist at this point.
            # Configure XenServer neutron specific items for q-domua and n-cpu
            config_nova_compute
            config_ovs_agent
            config_ceilometer
            ;;
        extra)
            # Called near the end after layer 1 and 2 services have been started
            start_ovs_agent
            start_ceilometer_acompute
            ;;
        test-config)
            # Called at the end of devstack used to configure tempest
            # or any other test environments
            iniset $TEMPEST_CONFIG compute hypervisor_type XenServer
            iniset $TEMPEST_CONFIG compute volume_device_name xvdb
            iniset $TEMPEST_CONFIG scenario img_file $DEFAULT_IMAGE_FILE_NAME
            # TODO(huanxie) Maybe we can set some conf here for CI?
            ;;
    esac
elif [[ "$MODE" == "unstack" ]]; then
    # Called by unstack.sh before other services are shut down
    stop_ovs_agent
    cleanup_dom0_iptables
elif [[ "$MODE" == "clean" ]]; then
    # Called by clean.sh before other services are cleaned, but after unstack.sh has been called
    cleanup_dom0_iptables
    # TODO(huanxie)
    # clean the OVS bridge created in Dom0?
fi