#!/bin/bash

# Idempotent script to apply heat configuration to the running network
# environment.
#
# If a public_interface_raw_device is defined in metadata and public_interface
# does not exist, the public_interface is used to derived a vlan id and a vlan
# interface is configured.
#
# If a bootstrap public_interface_ip is defined in metadata and not attached to
# any device then it will be added to the public_interface device if one is
# defined.
#
# An integration bridge for neutron-ovs-agent is created.
#
# If no physical bridge is defined in metadata, the script will have no
# further effect.
#
# If the defined physical_bridge is not present on the system, one will be
# defined via ovs-vsctl.
#
# The physical bridge will be brought up.
#
# If a public_interface is defined in metadata:
#   - it will be made a member of the physical_bridge.
#   - an ip addresses on public_interface are moved onto physical_bridge.
#
# If public_interface_route is set then the current default route is
# specialised to a 169.254.169.254/32 only route (unless there is already a
# 169.254.169.254 route - such as a neutron network with host routes can
# create) and a default route via public_interface_route is added on the public
# interface.
#
# Note that no persistent config file is written to the OS : ovs-vsctl modifies
# a persistent database so the bridge device will persist across reboots, but
# [on Ubuntu at least] early boot does not bring up ovs-vswitch early enough,
# and metadata access will fail.

set -eux

PATH=/usr/local/bin:$PATH

EXTERNAL_BRIDGE=$(os-config-applier --key neutron.ovs.physical_bridge --type raw --key-default '')
PHYSICAL_INTERFACE=$(os-config-applier --key neutron.ovs.public_interface --type raw  --key-default '')
PHYSICAL_INTERFACE_IP=$(os-config-applier --key bootstack.public_interface_ip --type netaddress  --key-default '')
PHYSICAL_INTERFACE_RAW_DEVICE=$(os-config-applier --key neutron.ovs.public_interface_raw_device --type raw --key-default '')
PUBLIC_INTERFACE_ROUTE=$(os-config-applier --key neutron.ovs.public_interface_route --type netaddress --key-default '')

if [ -n "$PHYSICAL_INTERFACE_RAW_DEVICE" ]; then
  if ! (ip link show dev $PHYSICAL_INTERFACE) ; then
    VLAN_ID=$(echo $PHYSICAL_INTERFACE | sed s/vlan//)
    vconfig set_name_type VLAN_PLUS_VID_NO_PAD
    vconfig add $PHYSICAL_INTERFACE_RAW_DEVICE $VLAN_ID
  fi
  ip link set $PHYSICAL_INTERFACE up
fi

if [ -n "$PHYSICAL_INTERFACE_IP" -a -n "$PHYSICAL_INTERFACE" ] ; then
    if ! (ip addr show | grep -q $PHYSICAL_INTERFACE_IP) ; then
        ip addr add $PHYSICAL_INTERFACE_IP dev $PHYSICAL_INTERFACE
    fi
fi

# Hacky: ensure the switch is running : we should instead arrange for this
# script to not be run before it's started.
service openvswitch-switch restart || service openvswitch restart

ovs-vsctl --no-wait -- --may-exist add-br br-int
ovs-vsctl --no-wait br-set-external-id br-int bridge-id br-int

if [ -z "$EXTERNAL_BRIDGE" ] ; then
    exit 0
fi

ovs-vsctl -- --may-exist add-br $EXTERNAL_BRIDGE
ovs-vsctl --no-wait br-set-external-id $EXTERNAL_BRIDGE bridge-id $EXTERNAL_BRIDGE

ip link set dev $EXTERNAL_BRIDGE up

if [ -n "$PHYSICAL_INTERFACE" ] ; then
    if ovs-vsctl port-to-br $PHYSICAL_INTERFACE ; then
        EXISTING_PORT=$(ovs-vsctl port-to-br $PHYSICAL_INTERFACE)
        if [ "$EXISTING_PORT" != "$PHYSICAL_INTERFACE" ] ; then
            ovs-vsctl del-port $EXTERNAL_BRIDGE $PHYSICAL_INTERFACE
        fi
    fi
    ovs-vsctl add-port $EXTERNAL_BRIDGE $PHYSICAL_INTERFACE
    ip link set dev $PHYSICAL_INTERFACE up
fi

if [ -n "$PHYSICAL_INTERFACE" ] ; then
    # Right now we probably are disconnected, need to move all IPs and routers
    # from interface to bridge.
    # Add all the IP addresses, so that we can add routes.
    # Then add routes
    # Then remove IP addresses
    # This way, if we are interrupted, nothing has been lost: we're temporarily
    # multipathed is all.
    IPS=$(ip addr show dev $PHYSICAL_INTERFACE | grep ' inet ' | awk '{print $2}')
    # Strictly speaking this doesn't grab all routes, but for now it's
    # sufficient.
    ROUTES=$(ip route show dev $PHYSICAL_INTERFACE | grep via || true)
    for IP in $IPS ; do
      if ! ip addr show $EXTERNAL_BRIDGE | grep $IP; then
        ip addr add $IP dev $EXTERNAL_BRIDGE
      fi
    done
    ORIG_IFS=$IFS
    IFS=$'\n'
    for ROUTE in $ROUTES ; do
        IFS=$ORIG_IFS
        ip route prepend dev $EXTERNAL_BRIDGE $ROUTE
        ip route del dev $PHYSICAL_INTERFACE $ROUTE
    done
    for IP in $IPS ; do
      ip addr del $IP dev $PHYSICAL_INTERFACE
    done
    # Handle default route replacement.
    if [ -n "$PUBLIC_INTERFACE_ROUTE" ]; then
        DEFAULT_VIA=$(ip route show | awk '/default / { print $3 }')
        if [ "$DEFAULT_VIA" != "$PUBLIC_INTERFACE_ROUTE" ]; then
          if [ -z "$(ip route show 169.254.169.254)" ]; then
            # No explicit route to 169.254.169.254 - set one.
            ip route add 169.254.169.254/32 via $DEFAULT_VIA
          fi
          ip route prepend dev $EXTERNAL_BRIDGE default via $PUBLIC_INTERFACE_ROUTE
          ip route del default via $DEFAULT_VIA
        fi
    fi
fi
