#!/bin/bash

{{/*
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.
*/}}

set -ex

OVS_SOCKET=/run/openvswitch/db.sock
chown neutron: ${OVS_SOCKET}

# This enables the usage of 'ovs-appctl' from neutron pod.
OVS_PID=$(cat /run/openvswitch/ovs-vswitchd.pid)
OVS_CTL=/run/openvswitch/ovs-vswitchd.${OVS_PID}.ctl
chown neutron: ${OVS_CTL}

function get_dpdk_config_value {
  values=$1
  filter=$2
  value=$(echo ${values} | jq -r ${filter})
  if [[ "${value}" == "null" ]]; then
    echo ""
  else
    echo "${value}"
  fi
}


DPDK_CONFIG_FILE=/tmp/dpdk.conf
DPDK_CONFIG=""
DPDK_ENABLED=false
if [ -f ${DPDK_CONFIG_FILE} ]; then
  DPDK_CONFIG=$(cat ${DPDK_CONFIG_FILE})
  if [[ $(get_dpdk_config_value ${DPDK_CONFIG} '.enabled') == "true" ]]; then
    DPDK_ENABLED=true
  fi
fi

function bind_nic {
  echo $2 > /sys/bus/pci/devices/$1/driver_override
  echo $1 > /sys/bus/pci/drivers/$2/bind
}

function unbind_nic {
  echo $1 > /sys/bus/pci/drivers/$2/unbind
  echo > /sys/bus/pci/devices/$1/driver_override
}

function get_name_by_pci_id {
  path=$(find /sys/bus/pci/devices/$1/ -name net)
  if [ -n "${path}" ] ; then
    echo $(ls -1 $path/)
  fi
}

function get_ip_address_from_interface {
  local interface=$1
  local ip=$(ip -4 -o addr s "${interface}" | awk '{ print $4; exit }' | awk -F '/' 'NR==1 {print $1}')
  if [ -z "${ip}" ] ; then
    exit 1
  fi
  echo ${ip}
}

function get_ip_prefix_from_interface {
  local interface=$1
  local prefix=$(ip -4 -o addr s "${interface}" | awk '{ print $4; exit }' | awk -F '/' 'NR==1 {print $2}')
  if [ -z "${prefix}" ] ; then
    exit 1
  fi
  echo ${prefix}
}

function migrate_ip {
  pci_id=$1
  bridge_name=$2

  local src_nic=$(get_name_by_pci_id ${pci_id})
  if [ -n "${src_nic}" ] ; then
    bridge_exists=$(ip a s "${bridge_name}" | grep "${bridge_name}" | cut -f2 -d':' 2> /dev/null)
    if [ -z "${bridge_exists}" ] ; then
      echo "Bridge "${bridge_name}" does not exist. Creating it on demand."
      init_ovs_dpdk_bridge "${bridge_name}"
    fi

    migrate_ip_from_nic ${src_nic} ${bridge_name}
  fi
}

function migrate_ip_from_nic {
  src_nic=$1
  bridge_name=$2

  # Enabling explicit error handling: We must avoid to lose the IP
  # address in the migration process. Hence, on every error, we
  # attempt to assign the IP back to the original NIC and exit.
  set +e

  ip=$(get_ip_address_from_interface ${src_nic})
  prefix=$(get_ip_prefix_from_interface ${src_nic})

  bridge_ip=$(get_ip_address_from_interface "${bridge_name}")
  bridge_prefix=$(get_ip_prefix_from_interface "${bridge_name}")

  ip link set ${bridge_name} up

  if [[ -n "${ip}" && -n "${prefix}" ]]; then
    ip addr flush dev ${src_nic}
    if [ $? -ne 0 ] ; then
      ip addr add ${ip}/${prefix} dev ${src_nic}
      echo "Error while flushing IP from ${src_nic}."
      exit 1
    fi

    ip addr add ${ip}/${prefix} dev "${bridge_name}"
    if [ $? -ne 0 ] ; then
      echo "Error assigning IP to bridge "${bridge_name}"."
      ip addr add ${ip}/${prefix} dev ${src_nic}
      exit 1
    fi
  elif [[ -n "${bridge_ip}" && -n "${bridge_prefix}" ]]; then
    echo "Bridge '${bridge_name}' already has IP assigned. Keeping the same:: IP:[${bridge_ip}]; Prefix:[${bridge_prefix}]..."
  elif [[ -z "${bridge_ip}" && -z "${ip}" ]]; then
    echo "Interface and bridge have no ips configured. Leaving as is."
  else
    echo "Interface ${name} has invalid IP address. IP:[${ip}]; Prefix:[${prefix}]..."
    exit 1
  fi

  set -e
}

function get_pf_or_vf_pci {
  dpdk_pci_id=${1}
  vf_index=${2}

  if [ -n "$vf_index" ]
  then
    iface=$(get_name_by_pci_id "${dpdk_pci_id}")
    sysfs_numvfs_path="/sys/class/net/${iface}/device/sriov_numvfs"
    if [[ -f /sys/class/net/${iface}/device/sriov_numvfs &&
          "$(cat /sys/class/net/${iface}/device/sriov_numvfs)" -ne "0" &&
          -e /sys/class/net/${iface}/device/virtfn${vf_index} ]]
    then
      dpdk_pci_id=$(ls -la /sys/class/net/${iface}/device/virtfn${vf_index})
      dpdk_pci_id=${dpdk_pci_id#*"../"}
    else
      echo "Error fetching the VF PCI for PF: ["${iface}", "${dpdk_pci_id}"] and VF-Index: ${vf_index}."
      exit 1
    fi
  fi
}

function bind_dpdk_nic {
  target_driver=${1}
  pci_id=${2}

  current_driver="$(get_driver_by_address "${pci_id}" )"
  if [ "$current_driver" != "$target_driver" ]; then
    if [ "$current_driver" != "" ]; then
      unbind_nic "${pci_id}" ${current_driver}
    fi
    bind_nic "${pci_id}" ${target_driver}
  fi
}

function process_dpdk_nics {
  target_driver=$(get_dpdk_config_value ${DPDK_CONFIG} '.driver')
  # loop over all nics
  echo $DPDK_CONFIG | jq -r -c '.nics[]' | \
  while IFS= read -r nic; do
    local port_name=$(get_dpdk_config_value ${nic} '.name')
    local pci_id=$(get_dpdk_config_value ${nic} '.pci_id')
    local bridge=$(get_dpdk_config_value ${nic} '.bridge')
    local vf_index=$(get_dpdk_config_value ${nic} '.vf_index')

    if [[ $(get_dpdk_config_value ${nic} '.migrate_ip') == true ]] ; then
      migrate_ip "${pci_id}" "${bridge}"
    fi

    iface=$(get_name_by_pci_id "${pci_id}")

    if [ -n "${iface}" ]; then
      ip link set ${iface} promisc on
      if [ -n "${vf_index}" ]; then
        vf_string="vf ${vf_index}"
        ip link set ${iface} ${vf_string} trust on

        # NOTE: To ensure proper toggle of spoofchk,
        # turn it on then off.
        ip link set ${iface} ${vf_string} spoofchk on
        ip link set ${iface} ${vf_string} spoofchk off
      fi
    fi

    # Fetch the PCI to be bound to DPDK driver.
    # In case VF Index is configured then PCI of that particular VF
    # is bound to DPDK, otherwise PF PCI is bound to DPDK.
    get_pf_or_vf_pci "${pci_id}" "${vf_index}"

    bind_dpdk_nic ${target_driver} "${dpdk_pci_id}"

    dpdk_options=""
    ofport_request=$(get_dpdk_config_value ${nic} '.ofport_request')
    if [ -n "${ofport_request}" ]; then
      dpdk_options+='ofport_request=${ofport_request} '
    fi
    n_rxq=$(get_dpdk_config_value ${nic} '.n_rxq')
    if [ -n "${n_rxq}" ]; then
      dpdk_options+='options:n_rxq=${n_rxq} '
    fi
    n_txq=$(get_dpdk_config_value ${nic} '.n_txq')
    if [ -n "${n_txq}" ]; then
      dpdk_options+='options:n_txq=${n_txq} '
    fi
    pmd_rxq_affinity=$(get_dpdk_config_value ${nic} '.pmd_rxq_affinity')
    if [ -n "${pmd_rxq_affinity}" ]; then
      dpdk_options+='other_config:pmd-rxq-affinity=${pmd_rxq_affinity} '
    fi
    mtu=$(get_dpdk_config_value ${nic} '.mtu')
    if [ -n "${mtu}" ]; then
      dpdk_options+='mtu_request=${mtu} '
    fi
    n_rxq_size=$(get_dpdk_config_value ${nic} '.n_rxq_size')
    if [ -n "${n_rxq_size}" ]; then
      dpdk_options+='options:n_rxq_desc=${n_rxq_size} '
    fi
    n_txq_size=$(get_dpdk_config_value ${nic} '.n_txq_size')
    if [ -n "${n_txq_size}" ]; then
      dpdk_options+='options:n_txq_desc=${n_txq_size} '
    fi
    vhost_iommu_support=$(get_dpdk_config_value ${nic} '.vhost-iommu-support')
    if [ -n "${vhost_iommu_support}" ]; then
      dpdk_options+='options:vhost-iommu-support=${vhost_iommu_support} '
    fi

    ovs-vsctl --db=unix:${OVS_SOCKET} --may-exist add-port ${bridge} ${port_name} \
       -- set Interface ${port_name} type=dpdk options:dpdk-devargs=${pci_id} ${dpdk_options}

  done
}

function process_dpdk_bonds {
  target_driver=$(get_dpdk_config_value ${DPDK_CONFIG} '.driver')
  # loop over all bonds
  echo $DPDK_CONFIG | jq -r -c '.bonds[]' > /tmp/bonds_array
  while IFS= read -r bond; do
    local bond_name=$(get_dpdk_config_value ${bond} '.name')
    local dpdk_bridge=$(get_dpdk_config_value ${bond} '.bridge')
    local migrate_ip=$(get_dpdk_config_value ${bond} '.migrate_ip')
    local mtu=$(get_dpdk_config_value ${bond} '.mtu')
    local n_rxq=$(get_dpdk_config_value ${bond} '.n_rxq')
    local n_txq=$(get_dpdk_config_value ${bond} '.n_txq')
    local ofport_request=$(get_dpdk_config_value ${bond} '.ofport_request')
    local n_rxq_size=$(get_dpdk_config_value ${bond} '.n_rxq_size')
    local n_txq_size=$(get_dpdk_config_value ${bond} '.n_txq_size')
    local vhost_iommu_support=$(get_dpdk_config_value ${bond} '.vhost-iommu-support')
    local ovs_options=$(get_dpdk_config_value ${bond} '.ovs_options')

    local nic_name_str=""
    local dev_args_str=""
    local ip_migrated=false

    echo $bond | jq -r -c '.nics[]' > /tmp/nics_array
    while IFS= read -r nic; do
      local pci_id=$(get_dpdk_config_value ${nic} '.pci_id')
      local nic_name=$(get_dpdk_config_value ${nic} '.name')
      local pmd_rxq_affinity=$(get_dpdk_config_value ${nic} '.pmd_rxq_affinity')
      local vf_index=$(get_dpdk_config_value ${nic} '.vf_index')
      local vf_string=""

      if [[ ${migrate_ip} = "true" && ${ip_migrated} = "false" ]]; then
        migrate_ip "${pci_id}" "${dpdk_bridge}"
        ip_migrated=true
      fi

      iface=$(get_name_by_pci_id "${pci_id}")

      if [ -n "${iface}" ]; then
        ip link set ${iface} promisc on
        if [ -n "${vf_index}" ]; then
          vf_string="vf ${vf_index}"
          ip link set ${iface} ${vf_string} trust on

          # NOTE: To ensure proper toggle of spoofchk,
          # turn it on then off.
          ip link set ${iface} ${vf_string} spoofchk on
          ip link set ${iface} ${vf_string} spoofchk off
        fi
      fi

      # Fetch the PCI to be bound to DPDK driver.
      # In case VF Index is configured then PCI of that particular VF
      # is bound to DPDK, otherwise PF PCI is bound to DPDK.
      get_pf_or_vf_pci "${pci_id}" "${vf_index}"

      bind_dpdk_nic ${target_driver} "${dpdk_pci_id}"

      nic_name_str+=" "${nic_name}""
      dev_args_str+=" -- set Interface "${nic_name}" type=dpdk options:dpdk-devargs=""${dpdk_pci_id}"

      if [[ -n ${mtu} ]]; then
        dev_args_str+=" -- set Interface "${nic_name}" mtu_request=${mtu}"
      fi

      if [[ -n ${n_rxq} ]]; then
        dev_args_str+=" -- set Interface "${nic_name}" options:n_rxq=${n_rxq}"
      fi

      if [[ -n ${n_txq} ]]; then
        dev_args_str+=" -- set Interface "${nic_name}" options:n_txq=${n_txq}"
      fi

      if [[ -n ${ofport_request} ]]; then
        dev_args_str+=" -- set Interface "${nic_name}" ofport_request=${ofport_request}"
      fi

      if [[ -n ${pmd_rxq_affinity} ]]; then
        dev_args_str+=" -- set Interface "${nic_name}" other_config:pmd-rxq-affinity=${pmd_rxq_affinity}"
      fi

      if [[ -n ${n_rxq_size} ]]; then
        dev_args_str+=" -- set Interface "${nic_name}" options:n_rxq_desc=${n_rxq_size}"
      fi

      if [[ -n ${n_txq_size} ]]; then
        dev_args_str+=" -- set Interface "${nic_name}" options:n_txq_desc=${n_txq_size}"
      fi

      if [[ -n ${vhost_iommu_support} ]]; then
        dev_args_str+=" -- set Interface "${nic_name}" options:vhost-iommu-support=${vhost_iommu_support}"
      fi
    done < /tmp/nics_array

    if [ "${UPDATE_DPDK_BOND_CONFIG}" == "true" ]; then
      echo -e "NOTE: UPDATE_DPDK_BOND_CONFIG is set to true.\
      \nThis might cause disruptions in ovs traffic.\
      \nTo avoid this disruption set UPDATE_DPDK_BOND_CONFIG to false."
      ovs-vsctl --db=unix:${OVS_SOCKET} set Bridge "${dpdk_bridge}" other_config:update_config=true
      ovs_update_config=true
    else
      ovs_update_config=$(ovs-vsctl --columns=other_config --no-heading -d json list bridge "${dpdk_bridge}" \
        | jq -r '.[1][] as $list | if $list[0] == "update_config" then $list[1] else empty end')
    fi


    if [ "${ovs_update_config}" == "true" ] || [ "${ovs_update_config}" == "" ];
    then
      ovs-vsctl --db=unix:${OVS_SOCKET} --if-exists del-port "${bond_name}"
      ovs-vsctl --db=unix:${OVS_SOCKET} set Bridge "${dpdk_bridge}" other_config:update_config=false
      ovs-vsctl --db=unix:${OVS_SOCKET} --may-exist add-bond "${dpdk_bridge}" "${bond_name}" \
        ${nic_name_str} \
        "${ovs_options}" ${dev_args_str}
    fi

  done < "/tmp/bonds_array"
}

function set_dpdk_module_log_level {
  # loop over all target modules
  if [ -n "$(get_dpdk_config_value ${DPDK_CONFIG} '.modules')" ]; then
    echo $DPDK_CONFIG | jq -r -c '.modules[]' > /tmp/modules_array
    while IFS= read -r module; do
      local mod_name=$(get_dpdk_config_value ${module} '.name')
      local mod_level=$(get_dpdk_config_value ${module} '.log_level')

      ovs-appctl -t ${OVS_CTL} vlog/set ${mod_name}:${mod_level}
      ovs-appctl -t ${OVS_CTL} vlog/list|grep ${mod_name}
    done < /tmp/modules_array
  fi
}

function get_driver_by_address {
  if [[ -e /sys/bus/pci/devices/$1/driver ]]; then
    echo $(ls /sys/bus/pci/devices/$1/driver -al | awk '{n=split($NF,a,"/"); print a[n]}')
  fi
}

function init_ovs_dpdk_bridge {
  bridge=$1
  ovs-vsctl --db=unix:${OVS_SOCKET} --may-exist add-br ${bridge} \
  -- set Bridge ${bridge} datapath_type=netdev
  ip link set ${bridge} up
}

# create all additional bridges defined in the DPDK section
function init_ovs_dpdk_bridges {
  for br in $(get_dpdk_config_value ${DPDK_CONFIG} '.bridges[].name'); do
    init_ovs_dpdk_bridge ${br}
  done
}

# handle any bridge mappings
# /tmp/auto_bridge_add is one line json file: {"br-ex1":"eth1","br-ex2":"eth2"}
for bmap in `sed 's/[{}"]//g' /tmp/auto_bridge_add | tr "," "\n"`
do
  bridge=${bmap%:*}
  iface=${bmap#*:}
  ovs-vsctl --no-wait --may-exist add-br $bridge
  if [ -n "$iface" ] && [ "$iface" != "null" ]
  then
    ovs-vsctl --no-wait --may-exist add-port $bridge $iface
    migrate_ip_from_nic $iface $bridge
    if [[ $(get_dpdk_config_value ${DPDK_CONFIG} '.enabled') != "true" ]]; then
      ip link set dev $iface up
    fi
  fi
done

tunnel_types="{{- .Values.conf.plugins.openvswitch_agent.agent.tunnel_types -}}"
if [[ -n "${tunnel_types}" ]] ; then
    tunnel_interface="{{- .Values.network.interface.tunnel -}}"
    if [ -z "${tunnel_interface}" ] ; then
        # search for interface with tunnel network routing
        tunnel_network_cidr="{{- .Values.network.interface.tunnel_network_cidr -}}"
        if [ -z "${tunnel_network_cidr}" ] ; then
            tunnel_network_cidr="0/0"
        fi
        # If there is not tunnel network gateway, exit
        tunnel_interface=$(ip -4 route list ${tunnel_network_cidr} | awk -F 'dev' '{ print $2; exit }' \
            | awk '{ print $1 }') || exit 1
    fi
fi

if [[ "${DPDK_ENABLED}" == "true" ]]; then
  init_ovs_dpdk_bridges
  process_dpdk_nics
  process_dpdk_bonds
  set_dpdk_module_log_level
fi

# determine local-ip dynamically based on interface provided but only if tunnel_types is not null
if [[ -n "${tunnel_types}" ]] ; then
  LOCAL_IP=$(get_ip_address_from_interface ${tunnel_interface})
  if [ -z "${LOCAL_IP}" ] ; then
    echo "Var LOCAL_IP is empty"
    exit 1
  fi

tee > /tmp/pod-shared/ml2-local-ip.ini << EOF
[ovs]
local_ip = "${LOCAL_IP}"
EOF

  if [[ "${DPDK_ENABLED}" == "true" ]]; then
    PREFIX=$(get_ip_prefix_from_interface "${tunnel_interface}")

    # loop over all nics
    echo $DPDK_CONFIG | jq -r -c '.bridges[]' | \
    while IFS= read -r br; do
      bridge_name=$(get_dpdk_config_value ${br} '.name')
      tunnel_underlay_vlan=$(get_dpdk_config_value ${br} '.tunnel_underlay_vlan')

      if [[ "${bridge_name}" == "${tunnel_interface}" ]]; then
        # Route the tunnel traffic via the physical bridge
        if [[ -n "${LOCAL_IP}" && -n "${PREFIX}" ]]; then
          if [[ -n $(ovs-appctl -t ${OVS_CTL} ovs/route/show | grep "${LOCAL_IP}" | grep -v '^Cached:') ]]; then
            ovs-appctl -t ${OVS_CTL} ovs/route/del "${LOCAL_IP}"/"${PREFIX}"
          fi
          ovs-appctl -t ${OVS_CTL} ovs/route/add "${LOCAL_IP}"/"${PREFIX}" "${tunnel_interface}"

          if [[ -n "${tunnel_underlay_vlan}" ]]; then
            # If there is not tunnel network gateway, exit
            IFS=. read -r i1 i2 i3 i4 <<< "${LOCAL_IP}"
            IFS=. read -r xx m1 m2 m3 m4 <<< $(for a in $(seq 1 32); do if [ $(((a - 1) % 8)) -eq 0 ]; then echo -n .; fi; if [ $a -le ${PREFIX} ]; then echo -n 1; else echo -n 0; fi; done)
            tunnel_network_cidr=$(printf "%d.%d.%d.%d\n" "$((i1 & (2#$m1)))" "$((i2 & (2#$m2)))" "$((i3 & (2#$m3)))" "$((i4 & (2#$m4)))") || exit 1
            # Put a new flow to tag all the tunnel traffic with configured vlan-id
            if [[ -n $(ovs-ofctl dump-flows "${tunnel_interface}" | grep "nw_dst=${tunnel_network_cidr}") ]]; then
              ovs-ofctl del-flows "${tunnel_interface}" "cookie=0x9999/-1, table=0, ip,nw_dst=${tunnel_network_cidr}"
            fi
            ovs-ofctl add-flow "${tunnel_interface}" "cookie=0x9999, table=0, priority=8, ip,nw_dst=${tunnel_network_cidr}, actions=mod_vlan_vid:${tunnel_underlay_vlan},NORMAL"
          fi
        fi
        break
      fi
    done
  fi
fi

{{- if and ( empty .Values.conf.neutron.DEFAULT.host ) ( .Values.pod.use_fqdn.neutron_agent ) }}
mkdir -p /tmp/pod-shared
tee > /tmp/pod-shared/neutron-agent.ini << EOF
[DEFAULT]
host = $(hostname --fqdn)
EOF
{{- end }}
