#!/bin/bash

#
# This script is used for setting up devstack VLAN config with provider VLAN and Openstack 
# VLAN networks. This scripts sets up three openstack networks 
# provider-vlan-11 (vlan id:11, subnet:10.0.11.0/24)
# provider-vlan-12 (vlan id:12, subnet:10.0.12.0/24)
# provider-vlan-13 (vlan id:13, subnet:10.0.13.0/24)
# provider-vlan-14 (vlan id:14, subnet:10.0.14.0/24)
# with which openstack instances can be launched and communicate with vthunder on provider 
# network.
#

function usage {
cat << EOF
usage: $0

OPTIONS:
  --help                     prints this message
  --setup-provider-vlan      sets up provider ovs bridge br-vlanp, neutron vlan config and
                             openstack vlan networks.
                             make sure to delete all non devstack entities created by user
                             in openstack before running this command. 
  --teardown-provider-vlan   tears down the openstack vlan networks, ovs bridge settings and
                             removes neutron vlan config.
                             make sure to delete all vlan entities created by user
                             in openstack before running this command.
EOF
}

function check_exists {
    local cmd="${1}"
    local pattern="${2}"

    local output=$(${cmd})
    local exists=$(echo "${output}" | grep "${pattern}")
    if [ -z "${exists}" ]
    then
        echo 0
        return
    fi

    echo 1
}

function host_install_packages {
    echo "[+] Installing virt-manager on host"
    sudo apt-get install -y virt-manager
}

function add_veth_pair {
    local interface_1="${1}"
    local interface_2="${2}"
    local ip_address="${3}"

    local exists=$(check_exists "ip link show" "${interface_1}")
    if [[ ${exists} -ne 1 ]]; then
        echo "[+] Creating veth pair ${interface_1}-${interface_2} on the host"
        sudo ip link add ${interface_1} type veth peer name ${interface_2}
        sudo ip link set ${interface_1} up
        sudo ip link set ${interface_2} up
        echo "[=] Setting ${ip_address}/24 on ${interface_1}"
        sudo ip addr add ${ip_address}/24 dev ${interface_1}
    fi
}

function add_tap_interface {
    local interface="${1}"

    exists=$(check_exists "ip link show" "${interface}")
    if [[ ${exists} -ne 1 ]]; then
        echo "[+] Creating tap interface ${interface} on the host"
        sudo ip tuntap add mode tap ${interface}
    fi
}

function setup_host_networking {
    add_veth_pair veth0 veth1 10.0.0.1
    add_veth_pair veth2 veth3 10.0.11.1
    add_veth_pair veth4 veth5 10.0.12.1
    add_veth_pair veth6 veth7 10.0.13.1
    add_veth_pair veth8 veth9 10.0.14.1

    add_tap_interface tap1
    add_tap_interface tap2
    add_tap_interface tap3
    add_tap_interface tap4

    pvlan_dhcp_running=$(ps -ef | grep -v grep | grep setup-vlanp-dnsmasq.pid)
    if [[ -z ${pvlan_dhcp_running} ]]; then
        echo "[+] Starting DHCP server for management and vlan networks" 
        sudo dnsmasq --no-hosts --pid-file=/var/run/setup-vlanp-dnsmasq.pid --dhcp-range=interface:vnet0,10.0.0.40,10.0.0.90,72h --dhcp-range=interface:vnet2,10.0.11.40,10.0.11.90,72h --dhcp-range=interface:vnet4,10.0.12.40,10.0.12.90,72h --dhcp-range=interface:vnet6,10.0.13.40,10.0.13.90,72h --dhcp-range=interface:vnet8,10.0.14.40,10.0.14.90,72h --dhcp-option=19,0 --local-service --bind-dynamic --conf-file=
    fi
}

function add_ovs_bridge {
    local bridge="${1}"

    local exists=$(check_exists "sudo ovs-vsctl list-br" "${bridge}")
    if [[ ${exists} -ne 1 ]]; then
        echo "[+] Creating OVS bridge ${bridge}"
        sudo ovs-vsctl add-br ${bridge}
    fi
}

function add_port_to_bridge {
    local bridge="${1}"
    local port="${2}"
    local tag="${3}"

    exists=$(check_exists "sudo ovs-vsctl list-ports ${bridge}" "${port}")
    if [[ ${exists} -ne 1 ]]; then
        if [[ ! -z ${tag} ]]; then
            echo "[+] Adding port ${port} to OVS bridge ${bridge} with tag ${tag}"
            sudo ovs-vsctl add-port ${bridge} ${port} tag=${tag}
        else
            echo "[+] Adding port ${port} to OVS bridge ${bridge}"
            sudo ovs-vsctl add-port ${bridge} ${port}
        fi
    fi
}

function setup_host_ovs_bridges {
    add_ovs_bridge br-mgmt
    add_port_to_bridge br-mgmt veth1 ""

    add_ovs_bridge br-vlanp
    add_port_to_bridge br-vlanp veth3 11
    add_port_to_bridge br-vlanp veth5 12
    add_port_to_bridge br-vlanp veth7 13
    add_port_to_bridge br-vlanp veth9 14

    add_port_to_bridge br-vlanp tap1 11
    add_port_to_bridge br-vlanp tap2 12
    add_port_to_bridge br-vlanp tap3 13
    add_port_to_bridge br-vlanp tap4 14
}

function patch_conf_files {
    echo "[+] Patching config files"

    exists=$(check_exists "/bin/cat /etc/neutron/neutron.conf" "service_plugins = neutron.services.l3_router.l3_router_plugin.L3RouterPlugin")
    if [[ ${exists} -eq 1 ]]; then
        echo "[=] Overwriting service_plugins in /etc/neutron/neutron.conf"
        sed -i "s/service_plugins = .*/service_plugins = /" /etc/neutron/neutron.conf
    fi

    exists=$(check_exists "/bin/cat /etc/neutron/plugins/ml2/ml2_conf.ini" "#type_drivers =")
    if [[ ${exists} -eq 1 ]]; then
        echo "[=] Overwriting type_drivers in /etc/neutron/plugins/ml2/ml2_conf.ini"
        sed -i "s/#type_drivers = /type_drivers = flat,vlan/" /etc/neutron/plugins/ml2/ml2_conf.ini
    fi

    exists=$(check_exists "/bin/cat /etc/neutron/plugins/ml2/ml2_conf.ini" "mechanism_drivers = openvswitch,linuxbridge")
    if [[ ${exists} -eq 1 ]]; then
        echo "[=] Overwriting mechanism_drivers in /etc/neutron/plugins/ml2/ml2_conf.ini"
        sed -i "s/mechanism_drivers = .*/mechanism_drivers = openvswitch/" /etc/neutron/plugins/ml2/ml2_conf.ini
    fi

    exists=$(check_exists "/bin/cat /etc/neutron/plugins/ml2/ml2_conf.ini" "tenant_network_types = vxlan")
    if [[ ${exists} -eq 1 ]]; then
        echo "[=] Overwriting tenant_network_types in /etc/neutron/plugins/ml2/ml2_conf.ini"
        sed -i "s/tenant_network_types = .*/tenant_network_types = /" /etc/neutron/plugins/ml2/ml2_conf.ini
    fi

    exists=$(check_exists "/bin/cat /etc/neutron/plugins/ml2/ml2_conf.ini" "network_vlan_ranges = public")
    if [[ ${exists} -eq 1 ]]; then
        echo "[=] Overwriting network_vlan_ranges /etc/neutron/plugins/ml2/ml2_conf.ini"
        sed -i "s/network_vlan_ranges = .*/network_vlan_ranges = provider/" /etc/neutron/plugins/ml2/ml2_conf.ini
    fi

    exists=$(check_exists "/bin/cat /etc/neutron/plugins/ml2/ml2_conf.ini" "provider:br-vlanp")
    if [[ ${exists} -ne 1 ]]; then
        echo "[=] Overwriting bridge_mappings in /etc/neutron/plugins/ml2/ml2_conf.ini"
        sed -i "s/bridge_mappings = .*/bridge_mappings = public:br-ex,provider:br-vlanp/" /etc/neutron/plugins/ml2/ml2_conf.ini
    fi

    exists=$(check_exists "/bin/cat /etc/neutron/dhcp_agent.ini" "#force_metadata =")
    if [[ ${exists} -eq 1 ]]; then
        echo "[=] Overwriting force_metadata in /etc/neutron/dhcp_agent.ini"
        sed -i "s/#force_metadata = .*/force_metadata = true/" /etc/neutron/dhcp_agent.ini
    fi
}

function restart_services {
    echo "[=] Restarting devstack@neutron-* services"
    pushd /etc/systemd/system/
    sudo systemctl restart devstack@q-*
    popd
}

function create_network {
    local vlan_id="${1}"
    local network_name="${2}"
    local subnet_name="${3}"
    local subnet_range="${4}"
    local allocation_pool="${5}"

    exists=$(check_exists "openstack network list" "${network_name}")
    if [[ ${exists} -ne 1 ]]; then
        echo "[+] Creating ${network_name}"
        openstack network create --external --provider-segment ${vlan_id} --provider-network-type vlan --provider-physical-network provider --share ${network_name}
        sleep 1
    fi

    exists=$(check_exists "openstack subnet list" "${subnet_name}")
    if [[ ${exists} -ne 1 ]]; then
        echo "[+] Creating ${subnet_name}"
        openstack subnet create --ip-version 4 --allocation-pool ${allocation_pool} --network ${network_name} --subnet-range ${subnet_range} ${subnet_name}
        sleep 1
    fi

    local snat_rule="POSTROUTING -s ${subnet_range} ! -d ${subnet_range} -j MASQUERADE"
    exists=$(check_exists "sudo iptables-save -t nat" "${snat_rule}")
    if [[ ${exists} -ne 1 ]]; then
        echo "[+] Creating SNAT rule ${snat_rule}"
        sudo iptables -t nat -A ${snat_rule}
    fi
}

function setup_provider_vlan {
    host_install_packages
    setup_host_networking
    setup_host_ovs_bridges

    patch_conf_files
    restart_services
    sleep 2

    create_network 11 provider-vlan-11 provider-vlan-11-subnet 10.0.11.0/24 start=10.0.11.100,end=10.0.11.200
    create_network 12 provider-vlan-12 provider-vlan-12-subnet 10.0.12.0/24 start=10.0.12.100,end=10.0.12.200
    create_network 13 provider-vlan-13 provider-vlan-13-subnet 10.0.13.0/24 start=10.0.13.100,end=10.0.13.200
    create_network 14 provider-vlan-14 provider-vlan-14-subnet 10.0.14.0/24 start=10.0.14.100,end=10.0.14.200
}

function delete_network {
    local network_name="${1}"
    local subnet_name="${2}"
    local subnet_range="${3}"

    local snat_rule="POSTROUTING -s ${subnet_range} ! -d ${subnet_range} -j MASQUERADE"
    local exists=$(check_exists "sudo iptables-save -t nat" "${snat_rule}")
    if [[ ${exists} -ne 1 ]]; then
        echo "[-] Deleting SNAT rule ${snat_rule}"
        sudo iptables -t nat -D ${snat_rule}
    fi

    exists=$(check_exists "openstack subnet list" "${subnet_name}")
    if [[ ${exists} -eq 1 ]]; then
        echo "[-] Deleting ${subnet_name}"
        openstack subnet delete ${subnet_name}
    fi

    exists=$(check_exists "openstack network list" "${network_name}")
    if [[ ${exists} -eq 1 ]]; then
        echo "[-] Deleting ${network_name}"
        openstack network delete ${network_name}
    fi
}

function delete_ostack_networks {
    delete_network provider-vlan-14 provider-vlan-14-subnet 10.0.14.0/24
    delete_network provider-vlan-13 provider-vlan-13-subnet 10.0.13.0/24
    delete_network provider-vlan-12 provider-vlan-12-subnet 10.0.12.0/24
    delete_network provider-vlan-11 provider-vlan-11-subnet 10.0.11.0/24
}

function unpatch_conf_files {
    echo "[-] Removing vlan patch in config files"

    local exists=$(check_exists "/bin/cat /etc/neutron/neutron.conf" "service_plugins = neutron.services.l3_router.l3_router_plugin.L3RouterPlugin")
    if [[ ${exists} -ne 1 ]]; then
        echo "[=] Overwriting service_plugins in /etc/neutron/neutron.conf"
        sed -i "s/service_plugins =/service_plugins = neutron.services.l3_router.l3_router_plugin.L3RouterPlugin/" /etc/neutron/neutron.conf
    fi

    exists=$(check_exists "/bin/cat /etc/neutron/plugins/ml2/ml2_conf.ini" "type_drivers = flat,vlan")
    if [[ ${exists} -eq 1 ]]; then
        echo "[=] Overwriting type_drivers in /etc/neutron/plugins/ml2/ml2_conf.ini"
        sed -i "s/type_drivers = flat,vlan/#type_drivers = /" /etc/neutron/plugins/ml2/ml2_conf.ini
    fi

    exists=$(check_exists "/bin/cat /etc/neutron/plugins/ml2/ml2_conf.ini" "mechanism_drivers = openvswitch,linuxbridge")
    if [[ ${exists} -ne 1 ]]; then
        echo "[=] Overwriting mechanism_drivers in /etc/neutron/plugins/ml2/ml2_conf.ini"
        sed -i "s/mechanism_drivers = .*/mechanism_drivers = openvswitch,linuxbridge/" /etc/neutron/plugins/ml2/ml2_conf.ini
    fi

    exists=$(check_exists "/bin/cat /etc/neutron/plugins/ml2/ml2_conf.ini" "tenant_network_types = vxlan")
    if [[ ${exists} -ne 1 ]]; then
        echo "[=] Overwriting tenant_network_types in /etc/neutron/plugins/ml2/ml2_conf.ini"
        sed -i "s/tenant_network_types =/tenant_network_types = vxlan/" /etc/neutron/plugins/ml2/ml2_conf.ini
    fi

    exists=$(check_exists "/bin/cat /etc/neutron/plugins/ml2/ml2_conf.ini" "network_vlan_ranges = provider")
    if [[ ${exists} -eq 1 ]]; then
        echo "[=] Overwriting network_vlan_ranges /etc/neutron/plugins/ml2/ml2_conf.ini"
        sed -i "s/network_vlan_ranges = provider/network_vlan_ranges = public/" /etc/neutron/plugins/ml2/ml2_conf.ini
    fi

    exists=$(check_exists "/bin/cat /etc/neutron/plugins/ml2/ml2_conf.ini" "provider:br-vlanp")
    if [[ ${exists} -eq 1 ]]; then
        echo "[=] Overwriting bridge_mappings in /etc/neutron/plugins/ml2/ml2_conf.ini"
        sed -i "s/bridge_mappings = public:br-ex,provider:br-vlanp/bridge_mappings = public:br-ex/" /etc/neutron/plugins/ml2/ml2_conf.ini
    fi
}

function delete_ovs_bridges {
    local exists=$(check_exists "sudo ovs-vsctl list-br" "br-vlanp")
    if [[ ${exists} -eq 1 ]]; then
        echo "[-] Deleting tap ports from br-vlanp"
        sudo ovs-vsctl del-port br-vlanp tap4
        sudo ovs-vsctl del-port br-vlanp tap3
        sudo ovs-vsctl del-port br-vlanp tap2
        sudo ovs-vsctl del-port br-vlanp tap1

        echo "[-] Deleting veth ports from br-vlanp"
        sudo ovs-vsctl del-port br-vlanp veth9
        sudo ovs-vsctl del-port br-vlanp veth7
        sudo ovs-vsctl del-port br-vlanp veth5
        sudo ovs-vsctl del-port br-vlanp veth3

        echo "[-] Deleting OVS bridge br-vlanp"
        sudo ovs-vsctl del-br br-vlanp
    fi

    exists=$(check_exists "sudo ovs-vsctl list-br" "br-mgmt")
    if [[ ${exists} -eq 1 ]]; then
        echo "[-] Deleting veth port from br-mgmt"
        sudo ovs-vsctl del-port br-mgmt veth1

        echo "[-] Deleting OVS bridge br-mgmt"
        sudo ovs-vsctl del-br br-mgmt
    fi
}

function delete_veth_pair {
    local interface="${1}"

    exists=$(check_exists "ip link show" "${interface}")
    if [[ ${exists} -eq 1 ]]; then
        echo "[-] Deleting ${interface} veth pair"
        sudo ip link del ${interface}
    fi
}

function delete_tap_interface {
    local interface="${1}"

    exists=$(check_exists "ip link show" "${interface}")
    if [[ ${exists} -eq 1 ]]; then
        echo "[-] Deleting ${interface} interface"
        sudo ip tuntap del mode tap ${interface}
    fi
}

function delete_host_tap_and_veth_interfaces {
    local pvlan_dhcp_pid=$(ps -ef | grep -v grep | grep setup-vlanp-dnsmasq.pid | awk '{print $2}')
    if [[ -n ${pvlan_dhcp_pid} ]]; then
        echo "[-] Stoping DHCP server for management and vlan networks"
        sudo kill -9 ${pvlan_dhcp_pid}
        sudo rm -f /var/run/setup-vlanp-dnsmasq.pid
    fi

    delete_veth_pair veth0
    delete_veth_pair veth2
    delete_veth_pair veth4
    delete_veth_pair veth6
    delete_veth_pair veth8

    delete_tap_interface tap4
    delete_tap_interface tap3
    delete_tap_interface tap2
    delete_tap_interface tap1
}

function teardown_provider_vlan {
    delete_ostack_networks
    unpatch_conf_files
    delete_ovs_bridges
    delete_host_tap_and_veth_interfaces
    restart_services
}

arg_setup_provider_vlan=false
arg_teardown_provider_vlan=false

function set_args {
    if [[ $# -eq 0 ]] || [[ $# -gt 1 ]]; then
        echo "Improper number of arguments. Pass only one argument."
        usage
        exit 0
    fi
    while [ "${1:-}" != "" ]; do
        case "$1" in
            "--help")
                usage
                exit 0
                ;;
            "--setup-provider-vlan")
                arg_setup_provider_vlan=true
                ;;
            "--teardown-provider-vlan")
                arg_teardown_provider_vlan=true
                ;;
            *)
                usage
                exit 0
        esac
        shift
    done
}

function main {
    set_args "$@"
    if [[ $arg_setup_provider_vlan = true ]]; then
        setup_provider_vlan
    else
        teardown_provider_vlan
    fi
}

main "$@"
