#!/bin/bash

# Idempotent script to create a ovs bridge and place a public_interface on the
# bridge.
#
# 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.
#   - all IP addresses on public_interface will be 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="$1"
PHYSICAL_INTERFACE="$2"
PUBLIC_INTERFACE_ROUTE="${3:-}"

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
