#
# Copyright 2012 Cisco Systems, Inc.
# All Rights Reserved.
#
#    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.
#
#
# Performs per host Linux Bridge configuration for Neutron.
# Based on the structure of the OpenVSwitch agent in the
# Neutron OpenVSwitch Plugin.

import sys

import netaddr
from neutron_lib.agent import topics
from neutron_lib import constants
from neutron_lib import exceptions
from neutron_lib.plugins import utils as plugin_utils
from neutron_lib.utils import helpers
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging
from oslo_service import service
from oslo_utils import excutils

from neutron._i18n import _
from neutron.agent.linux import bridge_lib
from neutron.agent.linux import ip_lib
from neutron.api.rpc.handlers import securitygroups_rpc as sg_rpc
from neutron.common import config as common_config
from neutron.common import profiler as setup_profiler
from neutron.common import utils
from neutron.conf.agent import common as agent_config
from neutron.conf import service as service_conf
from neutron.plugins.ml2.drivers.agent import _agent_manager_base as amb
from neutron.plugins.ml2.drivers.agent import _common_agent as ca
from neutron.plugins.ml2.drivers.l2pop.rpc_manager \
    import l2population_rpc as l2pop_rpc
from neutron.plugins.ml2.drivers.linuxbridge.agent import arp_protect
from neutron.plugins.ml2.drivers.linuxbridge.agent.common import config  # noqa
from neutron.plugins.ml2.drivers.linuxbridge.agent.common \
    import constants as lconst
from neutron.plugins.ml2.drivers.linuxbridge.agent.common \
    import utils as lb_utils
from neutron.plugins.ml2.drivers.linuxbridge.agent import \
    linuxbridge_agent_extension_api as agent_extension_api
from neutron.plugins.ml2.drivers.linuxbridge.agent \
    import linuxbridge_capabilities


LOG = logging.getLogger(__name__)

BRIDGE_NAME_PREFIX = "brq"
MAX_VLAN_POSTFIX_LEN = 5
VXLAN_INTERFACE_PREFIX = "vxlan-"

IPTABLES_DRIVERS = [
    'iptables',
    'iptables_hybrid',
    'neutron.agent.linux.iptables_firewall.IptablesFirewallDriver',
    'neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver'
]


class LinuxBridgeManager(amb.CommonAgentManagerBase):
    def __init__(self, bridge_mappings, interface_mappings):
        super(LinuxBridgeManager, self).__init__()
        self.bridge_mappings = bridge_mappings
        self.interface_mappings = interface_mappings
        self.validate_interface_mappings()
        self.validate_bridge_mappings()
        self.ip = ip_lib.IPWrapper()
        self.agent_api = None
        # VXLAN related parameters:
        self.local_ip = cfg.CONF.VXLAN.local_ip
        self.vxlan_mode = lconst.VXLAN_NONE
        if cfg.CONF.VXLAN.enable_vxlan:
            device = self.get_local_ip_device()
            self.validate_vxlan_group_with_local_ip()
            self.local_int = device.name
            self.check_vxlan_support()

    def validate_interface_mappings(self):
        for physnet, interface in self.interface_mappings.items():
            if not ip_lib.device_exists(interface):
                LOG.error("Interface %(intf)s for physical network %(net)s"
                          " does not exist. Agent terminated!",
                          {'intf': interface, 'net': physnet})
                sys.exit(1)

    def validate_bridge_mappings(self):
        for physnet, bridge in self.bridge_mappings.items():
            if not ip_lib.device_exists(bridge):
                LOG.error("Bridge %(brq)s for physical network %(net)s"
                          " does not exist. Agent terminated!",
                          {'brq': bridge, 'net': physnet})
                sys.exit(1)

    def _is_valid_multicast_range(self, mrange):
        try:
            addr, vxlan_min, vxlan_max = mrange.split(':')
            if int(vxlan_min) > int(vxlan_max):
                raise ValueError()
            try:
                local_ver = netaddr.IPAddress(self.local_ip).version
                n_addr = netaddr.IPAddress(addr)
                if not n_addr.is_multicast() or n_addr.version != local_ver:
                    raise ValueError()
            except netaddr.core.AddrFormatError:
                raise ValueError()
        except ValueError:
            return False
        return True

    def validate_vxlan_group_with_local_ip(self):
        for r in cfg.CONF.VXLAN.multicast_ranges:
            if not self._is_valid_multicast_range(r):
                LOG.error("Invalid multicast_range %(r)s. Must be in "
                          "<multicast address>:<vni_min>:<vni_max> format and "
                          "addresses must be in the same family as local IP "
                          "%(loc)s.", {'r': r, 'loc': self.local_ip})
                sys.exit(1)
        if not cfg.CONF.VXLAN.vxlan_group:
            return
        try:
            ip_addr = netaddr.IPAddress(self.local_ip)
            # Ensure the configured group address/range is valid and multicast
            group_net = netaddr.IPNetwork(cfg.CONF.VXLAN.vxlan_group)
            if not group_net.is_multicast():
                raise ValueError()
            if not ip_addr.version == group_net.version:
                raise ValueError()
        except (netaddr.core.AddrFormatError, ValueError):
            LOG.error("Invalid VXLAN Group: %(group)s, must be an address "
                      "or network (in CIDR notation) in a multicast "
                      "range of the same address family as local_ip: "
                      "%(ip)s",
                      {'group': cfg.CONF.VXLAN.vxlan_group,
                       'ip': self.local_ip})
            sys.exit(1)

    def get_local_ip_device(self):
        """Return the device with local_ip on the host."""
        device = self.ip.get_device_by_ip(self.local_ip)
        if not device:
            LOG.error("Tunneling cannot be enabled without the local_ip "
                      "bound to an interface on the host. Please "
                      "configure local_ip %s on the host interface to "
                      "be used for tunneling and restart the agent.",
                      self.local_ip)
            sys.exit(1)
        return device

    @staticmethod
    def get_bridge_name(network_id):
        if not network_id:
            LOG.warning("Invalid Network ID, will lead to incorrect "
                        "bridge name")
        bridge_name = BRIDGE_NAME_PREFIX + \
            network_id[:lconst.RESOURCE_ID_LENGTH]
        return bridge_name

    @staticmethod
    def get_subinterface_name(physical_interface, vlan_id):
        if not vlan_id:
            LOG.warning("Invalid VLAN ID, will lead to incorrect "
                        "subinterface name")
        vlan_postfix = '.%s' % vlan_id

        # For the vlan subinterface name prefix we use:
        # * the physical_interface, if len(physical_interface) +
        #   len(vlan_postifx) <= 15 for backward compatibility reasons
        #   Example: physical_interface = eth0
        #            prefix = eth0.1
        #            prefix = eth0.1111
        #
        # * otherwise a unique hash per physical_interface to help debugging
        #   Example: physical_interface = long_interface
        #            prefix = longHASHED.1
        #            prefix = longHASHED.1111
        #
        # Remark: For some physical_interface values, the used prefix can be
        # both, the physical_interface itself or a hash, depending
        # on the vlan_postfix length.
        # Example: physical_interface = mix_interface
        #          prefix = mix_interface.1 (backward compatible)
        #          prefix = mix_iHASHED.1111
        if (len(physical_interface) + len(vlan_postfix) >
                constants.DEVICE_NAME_MAX_LEN):
            physical_interface = plugin_utils.get_interface_name(
                physical_interface, max_len=(constants.DEVICE_NAME_MAX_LEN -
                                             MAX_VLAN_POSTFIX_LEN))
        return "%s%s" % (physical_interface, vlan_postfix)

    @staticmethod
    def get_tap_device_name(interface_id):
        return lb_utils.get_tap_device_name(interface_id)

    @staticmethod
    def get_vxlan_device_name(segmentation_id):
        if 0 <= int(segmentation_id) <= constants.MAX_VXLAN_VNI:
            return VXLAN_INTERFACE_PREFIX + str(segmentation_id)
        else:
            LOG.warning("Invalid Segmentation ID: %s, will lead to "
                        "incorrect vxlan device name", segmentation_id)

    @staticmethod
    def _match_multicast_range(segmentation_id):
        for mrange in cfg.CONF.VXLAN.multicast_ranges:
            addr, vxlan_min, vxlan_max = mrange.split(':')
            if int(vxlan_min) <= segmentation_id <= int(vxlan_max):
                return addr

    def get_vxlan_group(self, segmentation_id):
        mcast_addr = self._match_multicast_range(segmentation_id)
        if mcast_addr:
            net = netaddr.IPNetwork(mcast_addr)
        else:
            net = netaddr.IPNetwork(cfg.CONF.VXLAN.vxlan_group)
        # Map the segmentation ID to (one of) the group address(es)
        return str(net.network +
                   (int(segmentation_id) & int(net.hostmask)))

    def get_deletable_bridges(self):
        bridge_list = bridge_lib.get_bridge_names()
        bridges = {b for b in bridge_list if b.startswith(BRIDGE_NAME_PREFIX)}
        bridges.difference_update(self.bridge_mappings.values())
        return bridges

    @staticmethod
    def get_tap_devices_count(bridge_name):
        if_list = bridge_lib.BridgeDevice(bridge_name).get_interfaces()
        return len([interface for interface in if_list if
                    interface.startswith(constants.TAP_DEVICE_PREFIX)])

    def ensure_vlan_bridge(self, network_id, phy_bridge_name,
                           physical_interface, vlan_id):
        """Create a vlan and bridge unless they already exist."""
        interface = self.ensure_vlan(physical_interface, vlan_id)
        if phy_bridge_name:
            return self.ensure_bridge(phy_bridge_name)
        else:
            bridge_name = self.get_bridge_name(network_id)
            if self.ensure_bridge(bridge_name, interface):
                return interface

    def ensure_vxlan_bridge(self, network_id, segmentation_id, mtu):
        """Create a vxlan and bridge unless they already exist."""
        interface = self.ensure_vxlan(segmentation_id, mtu)
        if not interface:
            LOG.error("Failed creating vxlan interface for "
                      "%(segmentation_id)s",
                      {'segmentation_id': segmentation_id})
            return
        bridge_name = self.get_bridge_name(network_id)
        self.ensure_bridge(bridge_name, interface, update_interface=False)
        return interface

    def get_interface_details(self, interface, ip_version):
        device = self.ip.device(interface)
        ips = device.addr.list(scope='global',
                               ip_version=ip_version)

        # Update default gateway if necessary
        gateway = device.route.get_gateway(scope='global',
                                           ip_version=ip_version)
        return ips, gateway

    def ensure_flat_bridge(self, network_id, phy_bridge_name,
                           physical_interface):
        """Create a non-vlan bridge unless it already exists."""
        if phy_bridge_name:
            return self.ensure_bridge(phy_bridge_name)
        else:
            bridge_name = self.get_bridge_name(network_id)
            if self.ensure_bridge(bridge_name, physical_interface):
                return physical_interface

    def ensure_local_bridge(self, network_id, phy_bridge_name):
        """Create a local bridge unless it already exists."""
        if phy_bridge_name:
            bridge_name = phy_bridge_name
        else:
            bridge_name = self.get_bridge_name(network_id)
        return self.ensure_bridge(bridge_name)

    def ensure_vlan(self, physical_interface, vlan_id):
        """Create a vlan unless it already exists."""
        interface = self.get_subinterface_name(physical_interface, vlan_id)
        if not ip_lib.device_exists(interface):
            LOG.debug("Creating subinterface %(interface)s for "
                      "VLAN %(vlan_id)s on interface "
                      "%(physical_interface)s",
                      {'interface': interface, 'vlan_id': vlan_id,
                       'physical_interface': physical_interface})
            try:
                int_vlan = self.ip.add_vlan(interface, physical_interface,
                                            vlan_id)
            except RuntimeError:
                with excutils.save_and_reraise_exception() as ctxt:
                    if ip_lib.vlan_in_use(vlan_id):
                        ctxt.reraise = False
                        LOG.error("Unable to create VLAN interface for "
                                  "VLAN ID %s because it is in use by "
                                  "another interface.", vlan_id)
                        return
            int_vlan.disable_ipv6()
            int_vlan.link.set_up()
            LOG.debug("Done creating subinterface %s", interface)
        return interface

    def ensure_vxlan(self, segmentation_id, mtu=None):
        """Create a vxlan unless it already exists."""
        interface = self.get_vxlan_device_name(segmentation_id)
        if not ip_lib.device_exists(interface):
            LOG.debug("Creating vxlan interface %(interface)s for "
                      "VNI %(segmentation_id)s",
                      {'interface': interface,
                       'segmentation_id': segmentation_id})
            args = {'srcport': (cfg.CONF.VXLAN.udp_srcport_min,
                                cfg.CONF.VXLAN.udp_srcport_max),
                    'dstport': cfg.CONF.VXLAN.udp_dstport,
                    'ttl': cfg.CONF.VXLAN.ttl}
            if cfg.CONF.VXLAN.tos:
                args['tos'] = cfg.CONF.VXLAN.tos
                if cfg.CONF.AGENT.dscp or cfg.CONF.AGENT.dscp_inherit:
                    LOG.warning('The deprecated tos option in group VXLAN '
                                'is set and takes precedence over dscp and '
                                'dscp_inherit in group AGENT.')
            elif cfg.CONF.AGENT.dscp_inherit:
                args['tos'] = 'inherit'
            elif cfg.CONF.AGENT.dscp:
                args['tos'] = int(cfg.CONF.AGENT.dscp) << 2

            if self.vxlan_mode == lconst.VXLAN_MCAST:
                args['group'] = self.get_vxlan_group(segmentation_id)

            if cfg.CONF.VXLAN.l2_population:
                args['proxy'] = cfg.CONF.VXLAN.arp_responder
                # L2population should set the local ip address to handle
                # a source dev with multiple ip addresses configured.
                args['local'] = self.local_ip

            try:
                int_vxlan = self.ip.add_vxlan(interface, segmentation_id,
                                              self.local_int, **args)
            except RuntimeError:
                with excutils.save_and_reraise_exception() as ctxt:
                    # perform this check after an attempt rather than before
                    # to avoid excessive lookups and a possible race condition.
                    if ip_lib.vxlan_in_use(segmentation_id):
                        ctxt.reraise = False
                        LOG.error("Unable to create VXLAN interface for "
                                  "VNI %s because it is in use by another "
                                  "interface.", segmentation_id)
                        return None
            if mtu:
                try:
                    int_vxlan.link.set_mtu(mtu)
                except ip_lib.InvalidArgument:
                    phys_dev_mtu = ip_lib.get_device_mtu(self.local_int)
                    LOG.error("Provided MTU value %(mtu)s for VNI "
                              "%(segmentation_id)s is too high according "
                              "to physical device %(dev)s MTU=%(phys_mtu)s.",
                              {'mtu': mtu,
                               'segmentation_id': segmentation_id,
                               'dev': self.local_int,
                               'phys_mtu': phys_dev_mtu})
                    int_vxlan.link.delete()
                    return None
            int_vxlan.disable_ipv6()
            int_vxlan.link.set_up()
            LOG.debug("Done creating vxlan interface %s", interface)
        return interface

    def _update_interface_ip_details(self, destination, source, ips, gateway):
        dst_device = self.ip.device(destination)
        src_device = self.ip.device(source)

        # Append IP's to bridge if necessary
        if ips:
            for ip in ips:
                # If bridge ip address already exists, then don't add
                # otherwise will report error
                to = utils.cidr_to_ip(ip['cidr'])
                if not dst_device.addr.list(to=to):
                    dst_device.addr.add(cidr=ip['cidr'])

        if gateway:
            # Ensure that the gateway can be updated by changing the metric
            metric = 100
            ip_version = utils.get_ip_version(gateway['cidr'])
            if gateway['metric'] != ip_lib.IP_ROUTE_METRIC_DEFAULT[ip_version]:
                metric = gateway['metric'] - 1
            dst_device.route.add_gateway(gateway=gateway['via'],
                                         metric=metric)
            src_device.route.delete_gateway(gateway=gateway['via'])

        # Remove IP's from interface
        if ips:
            for ip in ips:
                src_device.addr.delete(cidr=ip['cidr'])

    def update_interface_ip_details(self, destination, source):
        # Returns True if there were IPs or a gateway moved
        updated = False
        for ip_version in (constants.IP_VERSION_4, constants.IP_VERSION_6):
            ips, gateway = self.get_interface_details(source, ip_version)
            if ips or gateway:
                self._update_interface_ip_details(destination, source, ips,
                                                  gateway)
                updated = True

        return updated

    def ensure_bridge(self, bridge_name, interface=None,
                      update_interface=True):
        """Create a bridge unless it already exists."""
        # ensure_device_is_ready instead of device_exists is used here
        # because there are cases where the bridge exists but it's not UP,
        # for example:
        # 1) A greenthread was executing this function and had not yet executed
        # "ip link set bridge_name up" before eventlet switched to this
        # thread running the same function
        # 2) The Nova VIF driver was running concurrently and had just created
        #    the bridge, but had not yet put it UP
        if not ip_lib.ensure_device_is_ready(bridge_name):
            LOG.debug("Starting bridge %(bridge_name)s for subinterface "
                      "%(interface)s",
                      {'bridge_name': bridge_name, 'interface': interface})
            bridge_device = bridge_lib.BridgeDevice.addbr(bridge_name)
            if not bridge_device.setfd(0):
                return
            if not bridge_device.disable_stp():
                return
            if bridge_device.link.set_up():
                return
            LOG.debug("Done starting bridge %(bridge_name)s for "
                      "subinterface %(interface)s",
                      {'bridge_name': bridge_name, 'interface': interface})
        else:
            bridge_device = bridge_lib.BridgeDevice(bridge_name)

        if not interface:
            return bridge_name

        # Update IP info if necessary
        if update_interface:
            self.update_interface_ip_details(bridge_name, interface)

        # Check if the interface is part of the bridge
        if not bridge_device.owns_interface(interface):
            # Check if the interface is attached to another bridge
            bridge = bridge_lib.BridgeDevice.get_interface_bridge(interface)
            if bridge:
                bridge.delif(interface)

            if not bridge_device.addif(interface):
                LOG.error("Unable to add %(interface)s to %(bridge_name)s",
                          {'interface': interface, 'bridge_name': bridge_name})
                return
        return bridge_name

    def ensure_physical_in_bridge(self, network_id,
                                  network_type,
                                  physical_network,
                                  segmentation_id,
                                  mtu):
        if network_type == constants.TYPE_VXLAN:
            if self.vxlan_mode == lconst.VXLAN_NONE:
                LOG.error("Unable to add vxlan interface for network %s",
                          network_id)
                return
            return self.ensure_vxlan_bridge(network_id, segmentation_id, mtu)

        # NOTE(nick-ma-z): Obtain mappings of physical bridge and interfaces
        physical_bridge = self.bridge_mappings.get(physical_network)
        physical_interface = self.interface_mappings.get(physical_network)
        if not physical_bridge and not physical_interface:
            LOG.error("No bridge or interface mappings"
                      " for physical network %s",
                      physical_network)
            return
        if network_type == constants.TYPE_FLAT:
            return self.ensure_flat_bridge(network_id, physical_bridge,
                                           physical_interface)
        elif network_type == constants.TYPE_VLAN:
            return self.ensure_vlan_bridge(network_id, physical_bridge,
                                           physical_interface,
                                           segmentation_id)
        else:
            LOG.error("Unknown network_type %(network_type)s for network "
                      "%(network_id)s.", {network_type: network_type,
                                          network_id: network_id})

    def add_tap_interface(self, network_id, network_type, physical_network,
                          segmentation_id, tap_device_name, device_owner, mtu):
        """Add tap interface and handle interface missing exceptions."""
        try:
            return self._add_tap_interface(network_id, network_type,
                                           physical_network, segmentation_id,
                                           tap_device_name, device_owner, mtu)
        except Exception:
            with excutils.save_and_reraise_exception() as ctx:
                if not ip_lib.device_exists(tap_device_name):
                    # the exception was likely a side effect of the tap device
                    # being removed during handling so we just return false
                    # like we would if it didn't exist to begin with.
                    ctx.reraise = False
                    return False

    def _add_tap_interface(self, network_id,
                           network_type, physical_network, segmentation_id,
                           tap_device_name, device_owner, mtu):
        """Add tap interface.

        If a VIF has been plugged into a network, this function will
        add the corresponding tap device to the relevant bridge.
        """
        if not ip_lib.device_exists(tap_device_name):
            LOG.debug("Tap device: %s does not exist on "
                      "this host, skipped", tap_device_name)
            return False

        bridge_name = self.bridge_mappings.get(physical_network)
        if not bridge_name:
            bridge_name = self.get_bridge_name(network_id)

        if network_type == constants.TYPE_LOCAL:
            self.ensure_local_bridge(network_id, bridge_name)
        elif not self.ensure_physical_in_bridge(network_id,
                                                network_type,
                                                physical_network,
                                                segmentation_id,
                                                mtu):
            return False
        if mtu:  # <-None with device_details from older neutron servers.
            # we ensure the MTU here because libvirt does not set the
            # MTU of a bridge it creates and the tap device it creates will
            # inherit from the bridge its plugged into, which will be 1500
            # at the time. See bug/1684326 for details.
            self._set_tap_mtu(tap_device_name, mtu)
        # Avoid messing with plugging devices into a bridge that the agent
        # does not own
        if not device_owner.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX):
            # Check if device needs to be added to bridge
            if not bridge_lib.BridgeDevice.get_interface_bridge(
                    tap_device_name):
                data = {'tap_device_name': tap_device_name,
                        'bridge_name': bridge_name}
                LOG.debug("Adding device %(tap_device_name)s to bridge "
                          "%(bridge_name)s", data)
                if not bridge_lib.BridgeDevice(bridge_name).addif(
                        tap_device_name):
                    return False
        else:
            data = {'tap_device_name': tap_device_name,
                    'device_owner': device_owner,
                    'bridge_name': bridge_name}
            LOG.debug("Skip adding device %(tap_device_name)s to "
                      "%(bridge_name)s. It is owned by %(device_owner)s and "
                      "thus added elsewhere.", data)
        return True

    @staticmethod
    def _set_tap_mtu(tap_device_name, mtu):
        ip_lib.IPDevice(tap_device_name).link.set_mtu(mtu)

    def plug_interface(self, network_id, network_segment, tap_name,
                       device_owner):
        return self.add_tap_interface(network_id, network_segment.network_type,
                                      network_segment.physical_network,
                                      network_segment.segmentation_id,
                                      tap_name, device_owner,
                                      network_segment.mtu)

    def delete_bridge(self, bridge_name):
        bridge_device = bridge_lib.BridgeDevice(bridge_name)
        if bridge_device.exists():
            physical_interfaces = set(self.interface_mappings.values())
            interfaces_on_bridge = bridge_device.get_interfaces()
            for interface in interfaces_on_bridge:
                self.remove_interface(bridge_name, interface)

                if interface.startswith(VXLAN_INTERFACE_PREFIX):
                    self.delete_interface(interface)
                else:
                    # Match the vlan/flat interface in the bridge.
                    # If the bridge has an IP, it mean that this IP was moved
                    # from the current interface, which also mean that this
                    # interface was not created by the agent.
                    updated = self.update_interface_ip_details(interface,
                                                               bridge_name)
                    if not updated and interface not in physical_interfaces:
                        self.delete_interface(interface)

            try:
                LOG.debug("Deleting bridge %s", bridge_name)
                if bridge_device.link.set_down():
                    return
                if bridge_device.delbr():
                    return
                LOG.debug("Done deleting bridge %s", bridge_name)
            except RuntimeError:
                with excutils.save_and_reraise_exception() as ctxt:
                    if not bridge_device.exists():
                        # the exception was likely a side effect of the bridge
                        # being removed by nova during handling,
                        # so we just return
                        ctxt.reraise = False
                        LOG.debug("Cannot delete bridge %s; it does not exist",
                                  bridge_name)
                        return
        else:
            LOG.debug("Cannot delete bridge %s; it does not exist",
                      bridge_name)

    @staticmethod
    def remove_interface(bridge_name, interface_name):
        bridge_device = bridge_lib.BridgeDevice(bridge_name)
        if bridge_device.exists():
            if not bridge_device.owns_interface(interface_name):
                return True
            LOG.debug("Removing device %(interface_name)s from bridge "
                      "%(bridge_name)s",
                      {'interface_name': interface_name,
                       'bridge_name': bridge_name})

            if bridge_device.delif(interface_name):
                LOG.debug("Done removing device %(interface_name)s from "
                          "bridge %(bridge_name)s",
                          {'interface_name': interface_name,
                           'bridge_name': bridge_name})
                return True
            else:
                if not bridge_device.owns_interface(interface_name):
                    LOG.debug("Cannot remove %(interface_name)s from "
                              "%(bridge_name)s. It is not on the bridge.",
                              {'interface_name': interface_name,
                               'bridge_name': bridge_name})
                    return False
                msg = _("Error deleting %(interface_name)s from bridge "
                        "%(bridge_name)s") % {'interface_name': interface_name,
                                              'bridge_name': bridge_name}
                raise RuntimeError(msg)
        else:
            LOG.debug("Cannot remove device %(interface_name)s bridge "
                      "%(bridge_name)s does not exist",
                      {'interface_name': interface_name,
                       'bridge_name': bridge_name})
            return False

    def delete_interface(self, interface):
        device = self.ip.device(interface)
        if device.exists():
            LOG.debug("Deleting interface %s",
                      interface)
            device.link.set_down()
            device.link.delete()
            LOG.debug("Done deleting interface %s", interface)

    def get_devices_modified_timestamps(self, devices):
        # NOTE(kevinbenton): we aren't returning real timestamps here. We
        # are returning interface indexes instead which change when the
        # interface is removed/re-added. This works for the direct
        # comparison the common agent loop performs with these.
        # See bug/1622833 for details.
        return {d: bridge_lib.get_interface_ifindex(d) for d in devices}

    @staticmethod
    def get_all_devices():
        devices = set()
        for device in bridge_lib.get_bridge_names():
            if device.startswith(constants.TAP_DEVICE_PREFIX):
                devices.add(device)
        return devices

    def vxlan_ucast_supported(self):
        if not cfg.CONF.VXLAN.l2_population:
            return False

        for seg_id in range(1, constants.MAX_VXLAN_VNI + 1):
            if (ip_lib.device_exists(self.get_vxlan_device_name(seg_id)) or
                    ip_lib.vxlan_in_use(seg_id)):
                continue
            test_iface = self.ensure_vxlan(seg_id)
            break
        else:
            LOG.error('No valid Segmentation ID to perform UCAST test.')
            return False

        ret = bridge_lib.FdbInterface.append(constants.FLOODING_ENTRY[0],
                                             test_iface, '1.1.1.1')
        self.delete_interface(test_iface)
        return ret

    @staticmethod
    def vxlan_mcast_supported():
        if not cfg.CONF.VXLAN.vxlan_group:
            LOG.warning('VXLAN muticast group(s) must be provided in '
                        'vxlan_group option to enable VXLAN MCAST mode')
            return False
        if not ip_lib.iproute_arg_supported(
                ['ip', 'link', 'add', 'type', 'vxlan'],
                'proxy'):
            LOG.warning('Option "%(option)s" must be supported by command '
                        '"%(command)s" to enable %(mode)s mode',
                        {'option': 'proxy',
                         'command': 'ip link add type vxlan',
                         'mode': 'VXLAN MCAST'})

            return False
        return True

    def check_vxlan_support(self):
        self.vxlan_mode = lconst.VXLAN_NONE

        if self.vxlan_ucast_supported():
            self.vxlan_mode = lconst.VXLAN_UCAST
        elif self.vxlan_mcast_supported():
            self.vxlan_mode = lconst.VXLAN_MCAST
        else:
            raise exceptions.VxlanNetworkUnsupported()
        LOG.debug('Using %s VXLAN mode', self.vxlan_mode)

    @staticmethod
    def fdb_ip_entry_exists(mac, ip, interface):
        ip_version = utils.get_ip_version(ip)
        entry = ip_lib.dump_neigh_entries(ip_version, interface, dst=ip,
                                          lladdr=mac)
        return bool(entry)

    @staticmethod
    def fdb_bridge_entry_exists(mac, interface, agent_ip=None):
        entries = bridge_lib.FdbInterface.show(dev=interface)
        macs = [entry['mac'] for entry in entries[interface]]
        ips = [entry['dst_ip'] for entry in entries[interface]]
        if not agent_ip:
            return mac in macs

        return agent_ip in ips and mac in macs

    @staticmethod
    def add_fdb_ip_entry(mac, ip, interface):
        if cfg.CONF.VXLAN.arp_responder:
            ip_lib.add_neigh_entry(ip, mac, interface)

    @staticmethod
    def remove_fdb_ip_entry(mac, ip, interface):
        if cfg.CONF.VXLAN.arp_responder:
            ip_lib.delete_neigh_entry(ip, mac, interface)

    def add_fdb_entries(self, agent_ip, ports, interface):
        for mac, ip in ports:
            if mac != constants.FLOODING_ENTRY[0]:
                self.add_fdb_ip_entry(mac, ip, interface)
                bridge_lib.FdbInterface.replace(mac, interface,
                                                dst_ip=agent_ip)
            elif self.vxlan_mode == lconst.VXLAN_UCAST:
                if self.fdb_bridge_entry_exists(mac, interface):
                    bridge_lib.FdbInterface.append(mac, interface,
                                                   dst_ip=agent_ip)
                else:
                    bridge_lib.FdbInterface.add(mac, interface,
                                                dst_ip=agent_ip)

    def remove_fdb_entries(self, agent_ip, ports, interface):
        for mac, ip in ports:
            if mac != constants.FLOODING_ENTRY[0]:
                self.remove_fdb_ip_entry(mac, ip, interface)
                bridge_lib.FdbInterface.delete(mac, interface, dst_ip=agent_ip)
            elif self.vxlan_mode == lconst.VXLAN_UCAST:
                bridge_lib.FdbInterface.delete(mac, interface, dst_ip=agent_ip)

    def get_agent_id(self):
        if self.bridge_mappings:
            mac = ip_lib.get_device_mac(
                list(self.bridge_mappings.values())[0])
        else:
            devices = self.ip.get_devices(True)
            for device in devices:
                mac = ip_lib.get_device_mac(device.name)
                if mac:
                    break
            else:
                LOG.error("Unable to obtain MAC address for unique ID. "
                          "Agent terminated!")
                sys.exit(1)
        return 'lb%s' % mac.replace(":", "")

    def get_agent_configurations(self):
        configurations = {'bridge_mappings': self.bridge_mappings,
                          'interface_mappings': self.interface_mappings
                          }
        if self.vxlan_mode != lconst.VXLAN_NONE:
            configurations['tunneling_ip'] = self.local_ip
            configurations['tunnel_types'] = [constants.TYPE_VXLAN]
            configurations['l2_population'] = cfg.CONF.VXLAN.l2_population
        return configurations

    def get_rpc_callbacks(self, context, agent, sg_agent):
        return LinuxBridgeRpcCallbacks(context, agent, sg_agent)

    def get_agent_api(self, **kwargs):
        if self.agent_api:
            return self.agent_api
        sg_agent = kwargs.get("sg_agent")
        iptables_manager = self._get_iptables_manager(sg_agent)
        self.agent_api = agent_extension_api.LinuxbridgeAgentExtensionAPI(
            iptables_manager)
        return self.agent_api

    @staticmethod
    def _get_iptables_manager(sg_agent):
        if not sg_agent:
            return None
        if cfg.CONF.SECURITYGROUP.firewall_driver in IPTABLES_DRIVERS:
            return sg_agent.firewall.iptables

    def get_rpc_consumers(self):
        consumers = [[topics.PORT, topics.UPDATE],
                     [topics.NETWORK, topics.DELETE],
                     [topics.NETWORK, topics.UPDATE],
                     [topics.SECURITY_GROUP, topics.UPDATE],
                     [topics.PORT_BINDING, topics.DEACTIVATE],
                     [topics.PORT_BINDING, topics.ACTIVATE]]
        if cfg.CONF.VXLAN.l2_population:
            consumers.append([topics.L2POPULATION, topics.UPDATE])
        return consumers

    def ensure_port_admin_state(self, tap_name, admin_state_up):
        LOG.debug("Setting admin_state_up to %s for device %s",
                  admin_state_up, tap_name)
        if admin_state_up:
            ip_lib.IPDevice(tap_name).link.set_up()
        else:
            ip_lib.IPDevice(tap_name).link.set_down()

    def setup_arp_spoofing_protection(self, device, device_details):
        arp_protect.setup_arp_spoofing_protection(device, device_details)

    def delete_arp_spoofing_protection(self, devices):
        arp_protect.delete_arp_spoofing_protection(devices)

    def delete_unreferenced_arp_protection(self, current_devices):
        arp_protect.delete_unreferenced_arp_protection(current_devices)

    def get_extension_driver_type(self):
        return lconst.EXTENSION_DRIVER_TYPE


class LinuxBridgeRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                              l2pop_rpc.L2populationRpcCallBackMixin,
                              amb.CommonAgentManagerRpcCallBackBase):

    # Set RPC API version to 1.0 by default.
    # history
    #   1.1 Support Security Group RPC
    #   1.3 Added param devices_to_update to security_groups_provider_updated
    #   1.4 Added support for network_update
    #   1.5 Added binding_activate and binding_deactivate
    target = oslo_messaging.Target(version='1.5')

    def network_delete(self, context, **kwargs):
        LOG.debug("network_delete received")
        network_id = kwargs.get('network_id')

        # NOTE(nick-ma-z): Don't remove pre-existing user-defined bridges
        if network_id in self.network_map:
            phynet = self.network_map[network_id].physical_network
            if phynet and phynet in self.agent.mgr.bridge_mappings:
                LOG.info("Physical network %s is defined in "
                         "bridge_mappings and cannot be deleted.",
                         network_id)
                return

        bridge_name = self.agent.mgr.get_bridge_name(network_id)
        LOG.debug("Delete %s", bridge_name)
        self.agent.mgr.delete_bridge(bridge_name)
        self.network_map.pop(network_id, None)

    def port_update(self, context, **kwargs):
        port_id = kwargs['port']['id']
        device_name = self.agent.mgr.get_tap_device_name(port_id)
        # Put the device name in the updated_devices set.
        # Do not store port details, as if they're used for processing
        # notifications there is no guarantee the notifications are
        # processed in the same order as the relevant API requests.
        self.updated_devices.add(device_name)
        LOG.debug("port_update RPC received for port: %s", port_id)

    def binding_deactivate(self, context, **kwargs):
        if kwargs.get('host') != cfg.CONF.host:
            return
        interface_name = self.agent.mgr.get_tap_device_name(
            kwargs.get('port_id'))
        bridge_name = self.agent.mgr.get_bridge_name(kwargs.get('network_id'))
        LOG.debug("Removing device %(interface_name)s from bridge "
                  "%(bridge_name)s due to binding being de-activated",
                  {'interface_name': interface_name,
                   'bridge_name': bridge_name})
        self.agent.mgr.remove_interface(bridge_name, interface_name)

    def binding_activate(self, context, **kwargs):
        if kwargs.get('host') != cfg.CONF.host:
            return
        # Since the common agent loop treats added and updated the same way,
        # just add activated ports to the updated devices list. This way,
        # adding binding activation is less disruptive to the existing code
        port_id = kwargs.get('port_id')
        device_name = self.agent.mgr.get_tap_device_name(port_id)
        self.updated_devices.add(device_name)
        LOG.debug("Binding activation received for port: %s", port_id)

    def network_update(self, context, **kwargs):
        network_id = kwargs['network']['id']
        LOG.debug("network_update message processed for network "
                  "%(network_id)s, with ports: %(ports)s",
                  {'network_id': network_id,
                   'ports': self.agent.network_ports[network_id]})
        for port_data in self.agent.network_ports[network_id]:
            self.updated_devices.add(port_data['device'])

    def fdb_add(self, context, fdb_entries):
        LOG.debug("fdb_add received")
        for network_id, values in fdb_entries.items():
            segment = self.network_map.get(network_id)
            if not segment:
                return

            if segment.network_type != constants.TYPE_VXLAN:
                return

            interface = self.agent.mgr.get_vxlan_device_name(
                segment.segmentation_id)

            agent_ports = values.get('ports')
            for agent_ip, ports in agent_ports.items():
                if agent_ip == self.agent.mgr.local_ip:
                    continue

                self.agent.mgr.add_fdb_entries(agent_ip,
                                               ports,
                                               interface)

    def fdb_remove(self, context, fdb_entries):
        LOG.debug("fdb_remove received")
        for network_id, values in fdb_entries.items():
            segment = self.network_map.get(network_id)
            if not segment:
                return

            if segment.network_type != constants.TYPE_VXLAN:
                return

            interface = self.agent.mgr.get_vxlan_device_name(
                segment.segmentation_id)

            agent_ports = values.get('ports')
            for agent_ip, ports in agent_ports.items():
                if agent_ip == self.agent.mgr.local_ip:
                    continue

                self.agent.mgr.remove_fdb_entries(agent_ip,
                                                  ports,
                                                  interface)

    def _fdb_chg_ip(self, context, fdb_entries):
        LOG.debug("update chg_ip received")
        for network_id, agent_ports in fdb_entries.items():
            segment = self.network_map.get(network_id)
            if not segment:
                return

            if segment.network_type != constants.TYPE_VXLAN:
                return

            interface = self.agent.mgr.get_vxlan_device_name(
                segment.segmentation_id)

            for agent_ip, state in agent_ports.items():
                if agent_ip == self.agent.mgr.local_ip:
                    continue

                after = state.get('after', [])
                for mac, ip in after:
                    self.agent.mgr.add_fdb_ip_entry(mac, ip, interface)

                before = state.get('before', [])
                for mac, ip in before:
                    self.agent.mgr.remove_fdb_ip_entry(mac, ip, interface)

    def fdb_update(self, context, fdb_entries):
        LOG.debug("fdb_update received")
        for action, values in fdb_entries.items():
            method = '_fdb_' + action
            if not hasattr(self, method):
                raise NotImplementedError()

            getattr(self, method)(context, values)


def main():
    common_config.register_common_config_options()
    common_config.init(sys.argv[1:])

    common_config.setup_logging()
    agent_config.setup_privsep()
    service_conf.register_service_opts(service_conf.RPC_EXTRA_OPTS, cfg.CONF)

    try:
        interface_mappings = helpers.parse_mappings(
            cfg.CONF.LINUX_BRIDGE.physical_interface_mappings)
    except ValueError as e:
        LOG.error("Parsing physical_interface_mappings failed: %s. "
                  "Agent terminated!", e)
        sys.exit(1)
    LOG.info("Interface mappings: %s", interface_mappings)

    try:
        bridge_mappings = helpers.parse_mappings(
            cfg.CONF.LINUX_BRIDGE.bridge_mappings)
    except ValueError as e:
        LOG.error("Parsing bridge_mappings failed: %s. "
                  "Agent terminated!", e)
        sys.exit(1)
    LOG.info("Bridge mappings: %s", bridge_mappings)

    manager = LinuxBridgeManager(bridge_mappings, interface_mappings)
    linuxbridge_capabilities.register()

    polling_interval = cfg.CONF.AGENT.polling_interval
    quitting_rpc_timeout = cfg.CONF.AGENT.quitting_rpc_timeout
    agent = ca.CommonAgentLoop(manager, polling_interval, quitting_rpc_timeout,
                               constants.AGENT_TYPE_LINUXBRIDGE,
                               constants.AGENT_PROCESS_LINUXBRIDGE)
    setup_profiler.setup(constants.AGENT_PROCESS_LINUXBRIDGE, cfg.CONF.host)
    LOG.info("Agent initialized successfully, now running... ")
    launcher = service.launch(cfg.CONF, agent, restart_method='mutate')
    launcher.wait()
