# -*- coding: utf-8 -*-

# Copyright 2014-2015 Red Hat, Inc.
#
# 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.

import glob
import logging
import os
import re

import os_net_config
from os_net_config import objects
from os_net_config import utils


logger = logging.getLogger(__name__)

# Import the raw NetConfig object so we can call its methods
netconfig = os_net_config.NetConfig()


def ifcfg_config_path(name):
    return "/etc/sysconfig/network-scripts/ifcfg-%s" % name


def remove_ifcfg_config(ifname):
    if re.match('[\w-]+$', ifname):
        ifcfg_file = ifcfg_config_path(ifname)
        if os.path.exists(ifcfg_file):
            os.remove(ifcfg_file)


# NOTE(dprince): added here for testability
def bridge_config_path(name):
    return ifcfg_config_path(name)


def ivs_config_path():
    return "/etc/sysconfig/ivs"


def nfvswitch_config_path():
    return "/etc/sysconfig/nfvswitch"


def vpp_config_path():
    return "/etc/vpp/startup.conf"


def route_config_path(name):
    return "/etc/sysconfig/network-scripts/route-%s" % name


def route6_config_path(name):
    return "/etc/sysconfig/network-scripts/route6-%s" % name


def cleanup_pattern():
    return "/etc/sysconfig/network-scripts/ifcfg-*"


def dhclient_path():
    if os.path.exists("/usr/sbin/dhclient"):
        return "/usr/sbin/dhclient"
    elif os.path.exists("/sbin/dhclient"):
        return "/sbin/dhclient"
    else:
        raise RuntimeError("Could not find dhclient")


def stop_dhclient_process(interface):
    """Stop a DHCP process when no longer needed.

    This method exists so that it may be stubbed out for unit tests.
    :param interface: The interface on which to stop dhclient.
    """
    pid_file = '/var/run/dhclient-%s.pid' % (interface)
    try:
        dhclient = dhclient_path()
    except RuntimeError as err:
        logger.info('Exception when stopping dhclient: %s' % err)
        return

    if os.path.exists(pid_file):
        msg = 'Stopping %s on interface %s' % (dhclient, interface)
        netconfig.execute(msg, dhclient, '-r', '-pf',
                          pid_file, interface)
        try:
            os.unlink(pid_file)
        except OSError as err:
            logger.error('Could not remove dhclient pid file \'%s\': %s' %
                         (pid_file, err))


class IfcfgNetConfig(os_net_config.NetConfig):
    """Configure network interfaces using the ifcfg format."""

    def __init__(self, noop=False, root_dir=''):
        super(IfcfgNetConfig, self).__init__(noop, root_dir)
        self.interface_data = {}
        self.ivsinterface_data = {}
        self.nfvswitch_intiface_data = {}
        self.nfvswitch_options = None
        self.vlan_data = {}
        self.route_data = {}
        self.route6_data = {}
        self.bridge_data = {}
        self.linuxbridge_data = {}
        self.linuxbond_data = {}
        self.ib_interface_data = {}
        self.linuxteam_data = {}
        self.vpp_interface_data = {}
        self.vpp_bond_data = {}
        self.member_names = {}
        self.renamed_interfaces = {}
        self.bond_primary_ifaces = {}
        logger.info('Ifcfg net config provider created.')

    def child_members(self, name):
        children = set()
        try:
            for member in self.member_names[name]:
                children.add(member)
                children.update(self.child_members(member))
        except KeyError:
            pass
        return children

    def _add_common(self, base_opt):

        ovs_extra = []
        data = "# This file is autogenerated by os-net-config\n"
        data += "DEVICE=%s\n" % base_opt.name
        if base_opt.onboot:
            data += "ONBOOT=yes\n"
        else:
            data += "ONBOOT=no\n"
        if isinstance(base_opt, objects.Interface) and base_opt.hotplug:
            data += "HOTPLUG=yes\n"
        else:
            data += "HOTPLUG=no\n"
        if base_opt.nm_controlled:
            data += "NM_CONTROLLED=yes\n"
        else:
            data += "NM_CONTROLLED=no\n"
        if not base_opt.dns_servers and not base_opt.use_dhcp:
            data += "PEERDNS=no\n"
        if isinstance(base_opt, objects.Vlan):
            if not base_opt.ovs_port:
                # vlans on OVS bridges are internal ports (no device, etc)
                data += "VLAN=yes\n"
                if base_opt.device:
                    data += "PHYSDEV=%s\n" % base_opt.device
                elif base_opt.linux_bond_name:
                    data += "PHYSDEV=%s\n" % base_opt.linux_bond_name
        elif isinstance(base_opt, objects.IvsInterface):
            data += "TYPE=IVSIntPort\n"
        elif isinstance(base_opt, objects.NfvswitchInternal):
            data += "TYPE=NFVSWITCHIntPort\n"
        elif isinstance(base_opt, objects.IbInterface):
            data += "TYPE=Infiniband\n"
            if base_opt.ethtool_opts:
                data += "ETHTOOL_OPTS=\"%s\"\n" % base_opt.ethtool_opts
        elif re.match('\w+\.\d+$', base_opt.name):
            data += "VLAN=yes\n"
        if base_opt.linux_bond_name:
            data += "MASTER=%s\n" % base_opt.linux_bond_name
            data += "SLAVE=yes\n"
        if base_opt.linux_team_name:
            data += "TEAM_MASTER=%s\n" % base_opt.linux_team_name
            if base_opt.primary:
                data += "TEAM_PORT_CONFIG='{\"prio\": 100}'\n"
        if base_opt.ivs_bridge_name:
            data += "DEVICETYPE=ivs\n"
            data += "IVS_BRIDGE=%s\n" % base_opt.ivs_bridge_name
        if base_opt.nfvswitch_bridge_name:
            data += "DEVICETYPE=nfvswitch\n"
            data += "NFVSWITCH_BRIDGE=%s\n" % base_opt.nfvswitch_bridge_name
        if base_opt.ovs_port:
            if not isinstance(base_opt, objects.LinuxTeam):
                data += "DEVICETYPE=ovs\n"
            if base_opt.bridge_name:
                if isinstance(base_opt, objects.Vlan):
                    data += "TYPE=OVSIntPort\n"
                    data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
                    data += "OVS_OPTIONS=\"tag=%s\"\n" % base_opt.vlan_id
                else:
                    data += "TYPE=OVSPort\n"
                    data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
        if base_opt.linux_bridge_name:
            data += "BRIDGE=%s\n" % base_opt.linux_bridge_name
        if isinstance(base_opt, objects.OvsBridge):
            data += "DEVICETYPE=ovs\n"
            data += "TYPE=OVSBridge\n"
            if base_opt.use_dhcp:
                data += "OVSBOOTPROTO=dhcp\n"
            if base_opt.members:
                members = [member.name for member in base_opt.members]
                self.member_names[base_opt.name] = members
                if base_opt.use_dhcp:
                    data += ("OVSDHCPINTERFACES=\"%s\"\n" % " ".join(members))
            if base_opt.primary_interface_name:
                mac = utils.interface_mac(base_opt.primary_interface_name)
                ovs_extra.append("set bridge %s other-config:hwaddr=%s" %
                                 (base_opt.name, mac))
            if base_opt.ovs_options:
                data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options
            ovs_extra.extend(base_opt.ovs_extra)
        elif isinstance(base_opt, objects.OvsUserBridge):
            data += "DEVICETYPE=ovs\n"
            data += "TYPE=OVSUserBridge\n"
            if base_opt.use_dhcp:
                data += "OVSBOOTPROTO=dhcp\n"
            if base_opt.members:
                members = [member.name for member in base_opt.members]
                self.member_names[base_opt.name] = members
                if base_opt.use_dhcp:
                    data += ("OVSDHCPINTERFACES=\"%s\"\n" % " ".join(members))
            if base_opt.ovs_options:
                data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options
            ovs_extra.extend(base_opt.ovs_extra)
        elif isinstance(base_opt, objects.OvsBond):
            if base_opt.primary_interface_name:
                primary_name = base_opt.primary_interface_name
                self.bond_primary_ifaces[base_opt.name] = primary_name
            data += "DEVICETYPE=ovs\n"
            data += "TYPE=OVSBond\n"
            if base_opt.use_dhcp:
                data += "OVSBOOTPROTO=dhcp\n"
            if base_opt.members:
                members = [member.name for member in base_opt.members]
                self.member_names[base_opt.name] = members
                data += ("BOND_IFACES=\"%s\"\n" % " ".join(members))
            if base_opt.ovs_options:
                data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options
            ovs_extra.extend(base_opt.ovs_extra)
        elif isinstance(base_opt, objects.LinuxBridge):
            data += "TYPE=Bridge\n"
            data += "DELAY=0\n"
            if base_opt.use_dhcp:
                data += "BOOTPROTO=dhcp\n"
            if base_opt.members:
                members = [member.name for member in base_opt.members]
                self.member_names[base_opt.name] = members
            if base_opt.primary_interface_name:
                primary_name = base_opt.primary_interface_name
                primary_mac = utils.interface_mac(primary_name)
                data += "MACADDR=\"%s\"\n" % primary_mac
        elif isinstance(base_opt, objects.LinuxBond):
            if base_opt.primary_interface_name:
                primary_name = base_opt.primary_interface_name
                primary_mac = utils.interface_mac(primary_name)
                data += "MACADDR=\"%s\"\n" % primary_mac
            if base_opt.use_dhcp:
                data += "BOOTPROTO=dhcp\n"
            if base_opt.members:
                members = [member.name for member in base_opt.members]
                self.member_names[base_opt.name] = members
            if base_opt.bonding_options:
                data += "BONDING_OPTS=\"%s\"\n" % base_opt.bonding_options
        elif isinstance(base_opt, objects.LinuxTeam):
            if base_opt.primary_interface_name:
                primary_name = base_opt.primary_interface_name
                primary_mac = utils.interface_mac(primary_name)
                data += "MACADDR=\"%s\"\n" % primary_mac
            if base_opt.use_dhcp:
                data += "BOOTPROTO=dhcp\n"
            if base_opt.members:
                members = [member.name for member in base_opt.members]
                self.member_names[base_opt.name] = members
            data += "DEVICETYPE=Team\n"
            if base_opt.bonding_options:
                data += "TEAM_CONFIG='%s'\n" % base_opt.bonding_options
        elif isinstance(base_opt, objects.OvsTunnel):
            ovs_extra.extend(base_opt.ovs_extra)
            data += "DEVICETYPE=ovs\n"
            data += "TYPE=OVSTunnel\n"
            data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
            data += "OVS_TUNNEL_TYPE=%s\n" % base_opt.tunnel_type
            data += "OVS_TUNNEL_OPTIONS=\"%s\"\n" % \
                    ' '.join(base_opt.ovs_options)
        elif isinstance(base_opt, objects.OvsPatchPort):
            ovs_extra.extend(base_opt.ovs_extra)
            data += "DEVICETYPE=ovs\n"
            data += "TYPE=OVSPatchPort\n"
            data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
            data += "OVS_PATCH_PEER=%s\n" % base_opt.peer
        elif isinstance(base_opt, objects.OvsDpdkPort):
            ovs_extra.extend(base_opt.ovs_extra)
            data += "DEVICETYPE=ovs\n"
            data += "TYPE=OVSDPDKPort\n"
            data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
            # Validation of DPDK port having only one interface is done prior
            # to this. So accesing the interface name statically.
            # Also dpdk_devargs would be valid here, since
            # bind_dpdk_interfaces() is invoked before this.
            dpdk_devargs = utils.get_dpdk_devargs(
                base_opt.members[0].name, self.noop)

            ovs_extra.append("set Interface $DEVICE options:dpdk-devargs="
                             "%s" % dpdk_devargs)
            if base_opt.mtu:
                ovs_extra.append("set Interface $DEVICE mtu_request=$MTU")
            if base_opt.rx_queue:
                data += "RX_QUEUE=%i\n" % base_opt.rx_queue
                ovs_extra.append("set Interface $DEVICE " +
                                 "options:n_rxq=$RX_QUEUE")
        elif isinstance(base_opt, objects.OvsDpdkBond):
            ovs_extra.extend(base_opt.ovs_extra)
            # Referring to bug:1643026, the below commenting of the interfaces,
            # is to workaround the error, but is not the long term solution.
            # The long term solution is to run DPDK options before
            # os-net-config, which is being tracked at BUG:1654975
            # if base_opt.primary_interface_name:
            #    primary_name = base_opt.primary_interface_name
            #    self.bond_primary_ifaces[base_opt.name] = primary_name
            data += "DEVICETYPE=ovs\n"
            data += "TYPE=OVSDPDKBond\n"
            data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
            if base_opt.members:
                for bond_member in base_opt.members:
                    # Validation of DPDK port having only one interface is done
                    # prior to this. So accesing the interface name statically.
                    # Also dpdk_devargs would be valid here, since
                    # bind_dpdk_interfaces () is invoked before this.
                    dpdk_devargs = utils.get_dpdk_devargs(
                        bond_member.members[0].name, self.noop)
                    ovs_extra.append("set Interface %s options:"
                                     "dpdk-devargs=%s"
                                     % (bond_member.name, dpdk_devargs))
                members = [member.name for member in base_opt.members]
                data += ("BOND_IFACES=\"%s\"\n" % " ".join(members))
                # MTU configuration given for the OvsDpdkbond shall be applied
                # to each of the members of the OvsDpdkbond
                if base_opt.mtu:
                    for member in base_opt.members:
                        ovs_extra.append("set Interface %s mtu_request=$MTU" %
                                         member.name)
                if base_opt.rx_queue:
                    data += "RX_QUEUE=%i\n" % base_opt.rx_queue
                    for member in base_opt.members:
                        ovs_extra.append("set Interface %s options:n_rxq="
                                         "$RX_QUEUE" % member.name)
            if base_opt.ovs_options:
                data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options
            ovs_extra.extend(base_opt.ovs_extra)
        else:
            if base_opt.use_dhcp:
                data += "BOOTPROTO=dhcp\n"
            elif not base_opt.addresses:
                data += "BOOTPROTO=none\n"
        if isinstance(base_opt, objects.Interface):
            if base_opt.ethtool_opts:
                data += "ETHTOOL_OPTS=\"%s\"\n" % base_opt.ethtool_opts

        if base_opt.mtu:
            data += "MTU=%i\n" % base_opt.mtu
        if base_opt.use_dhcpv6 or base_opt.v6_addresses():
            data += "IPV6INIT=yes\n"
            if base_opt.mtu:
                data += "IPV6_MTU=%i\n" % base_opt.mtu
        if base_opt.use_dhcpv6:
            data += "DHCPV6C=yes\n"
        elif base_opt.addresses:
            v4_addresses = base_opt.v4_addresses()
            if v4_addresses:
                data += "BOOTPROTO=static\n"
                for i, address in enumerate(v4_addresses):
                    num = '%s' % i if i else ''
                    data += "IPADDR%s=%s\n" % (num, address.ip)
                    data += "NETMASK%s=%s\n" % (num, address.netmask)

            v6_addresses = base_opt.v6_addresses()
            if v6_addresses:
                first_v6 = v6_addresses[0]
                data += "IPV6_AUTOCONF=no\n"
                data += "IPV6ADDR=%s\n" % first_v6.ip_netmask
                if len(v6_addresses) > 1:
                    secondaries_v6 = " ".join(map(lambda a: a.ip_netmask,
                                                  v6_addresses[1:]))
                    data += "IPV6ADDR_SECONDARIES=\"%s\"\n" % secondaries_v6

        if base_opt.hwaddr:
            data += "HWADDR=%s\n" % base_opt.hwaddr
        if ovs_extra:
            data += "OVS_EXTRA=\"%s\"\n" % " -- ".join(ovs_extra)
        if not base_opt.defroute:
            data += "DEFROUTE=no\n"
        if base_opt.dhclient_args:
            data += "DHCLIENTARGS=%s\n" % base_opt.dhclient_args
        if base_opt.dns_servers:
            data += "DNS1=%s\n" % base_opt.dns_servers[0]
            if len(base_opt.dns_servers) >= 2:
                data += "DNS2=%s\n" % base_opt.dns_servers[1]
                if len(base_opt.dns_servers) > 2:
                    logger.warning('ifcfg format supports max 2 resolvers.')
        return data

    def _add_routes(self, interface_name, routes=[]):
        logger.info('adding custom route for interface: %s' % interface_name)
        data = ""
        first_line = ""
        data6 = ""
        first_line6 = ""
        for route in routes:
            options = ""
            if route.route_options:
                options = " %s" % (route.route_options)
            if ":" not in route.next_hop:
                # Route is an IPv4 route
                if route.default:
                    first_line = "default via %s dev %s%s\n" % (
                                 route.next_hop, interface_name,
                                 options)
                else:
                    data += "%s via %s dev %s%s\n" % (
                            route.ip_netmask, route.next_hop,
                            interface_name, options)
            else:
                # Route is an IPv6 route
                if route.default:
                    first_line6 = "default via %s dev %s%s\n" % (
                                  route.next_hop, interface_name,
                                  options)
                else:
                    data6 += "%s via %s dev %s%s\n" % (
                             route.ip_netmask, route.next_hop,
                             interface_name, options)
        self.route_data[interface_name] = first_line + data
        self.route6_data[interface_name] = first_line6 + data6
        logger.debug('route data: %s' % self.route_data[interface_name])
        logger.debug('ipv6 route data: %s' % self.route6_data[interface_name])

    def add_interface(self, interface):
        """Add an Interface object to the net config object.

        :param interface: The Interface object to add.
        """
        logger.info('adding interface: %s' % interface.name)
        data = self._add_common(interface)
        logger.debug('interface data: %s' % data)
        self.interface_data[interface.name] = data
        if interface.routes:
            self._add_routes(interface.name, interface.routes)

        if interface.renamed:
            logger.info("Interface %s being renamed to %s"
                        % (interface.hwname, interface.name))
            self.renamed_interfaces[interface.hwname] = interface.name

    def add_vlan(self, vlan):
        """Add a Vlan object to the net config object.

        :param vlan: The vlan object to add.
        """
        logger.info('adding vlan: %s' % vlan.name)
        data = self._add_common(vlan)
        logger.debug('vlan data: %s' % data)
        self.vlan_data[vlan.name] = data
        if vlan.routes:
            self._add_routes(vlan.name, vlan.routes)

    def add_ivs_interface(self, ivs_interface):
        """Add a ivs_interface object to the net config object.

        :param ivs_interface: The ivs_interface object to add.
        """
        logger.info('adding ivs_interface: %s' % ivs_interface.name)
        data = self._add_common(ivs_interface)
        logger.debug('ivs_interface data: %s' % data)
        self.ivsinterface_data[ivs_interface.name] = data
        if ivs_interface.routes:
            self._add_routes(ivs_interface.name, ivs_interface.routes)

    def add_nfvswitch_internal(self, nfvswitch_internal):
        """Add a nfvswitch_internal interface object to the net config object.

        :param nfvswitch_internal: The nfvswitch_internal object to add.
        """
        iface_name = nfvswitch_internal.name
        logger.info('adding nfvswitch_internal interface: %s' % iface_name)
        data = self._add_common(nfvswitch_internal)
        logger.debug('nfvswitch_internal interface data: %s' % data)
        self.nfvswitch_intiface_data[iface_name] = data
        if nfvswitch_internal.routes:
            self._add_routes(iface_name, nfvswitch_internal.routes)

    def add_bridge(self, bridge):
        """Add an OvsBridge object to the net config object.

        :param bridge: The OvsBridge object to add.
        """
        logger.info('adding bridge: %s' % bridge.name)
        data = self._add_common(bridge)
        logger.debug('bridge data: %s' % data)
        self.bridge_data[bridge.name] = data
        if bridge.routes:
            self._add_routes(bridge.name, bridge.routes)

    def add_ovs_user_bridge(self, bridge):
        """Add an OvsUserBridge object to the net config object.

        :param bridge: The OvsUserBridge object to add.
        """
        logger.info('adding ovs user bridge: %s' % bridge.name)
        data = self._add_common(bridge)
        logger.debug('ovs user bridge data: %s' % data)
        self.bridge_data[bridge.name] = data
        if bridge.routes:
            self._add_routes(bridge.name, bridge.routes)

    def add_linux_bridge(self, bridge):
        """Add a LinuxBridge object to the net config object.

        :param bridge: The LinuxBridge object to add.
        """
        logger.info('adding linux bridge: %s' % bridge.name)
        data = self._add_common(bridge)
        logger.debug('bridge data: %s' % data)
        self.linuxbridge_data[bridge.name] = data
        if bridge.routes:
            self._add_routes(bridge.name, bridge.routes)

    def add_ivs_bridge(self, bridge):
        """Add a IvsBridge object to the net config object.

        IVS can only support one virtual switch per node,
        using "ivs" as its name. As long as the ivs service
        is running, the ivs virtual switch will be there.
        It is impossible to add multiple ivs virtual switches
        per node.
        :param bridge: The IvsBridge object to add.
        """
        pass

    def add_nfvswitch_bridge(self, bridge):
        """Add a NFVSwitchBridge object to the net config object.

        NFVSwitch can only support one virtual switch per node,
        using "nfvswitch" as its name. As long as the nfvswitch service
        is running, the nfvswitch virtual switch will be available.
        :param bridge: The NfvswitchBridge object to add.
        """
        self.nfvswitch_options = bridge.options

    def add_bond(self, bond):
        """Add an OvsBond object to the net config object.

        :param bond: The OvsBond object to add.
        """
        logger.info('adding bond: %s' % bond.name)
        data = self._add_common(bond)
        logger.debug('bond data: %s' % data)
        self.interface_data[bond.name] = data
        if bond.routes:
            self._add_routes(bond.name, bond.routes)

    def add_linux_bond(self, bond):
        """Add a LinuxBond object to the net config object.

        :param bond: The LinuxBond object to add.
        """
        logger.info('adding linux bond: %s' % bond.name)
        data = self._add_common(bond)
        logger.debug('bond data: %s' % data)
        self.linuxbond_data[bond.name] = data
        if bond.routes:
            self._add_routes(bond.name, bond.routes)

    def add_linux_team(self, team):
        """Add a LinuxTeam object to the net config object.

        :param team: The LinuxTeam object to add.
        """
        logger.info('adding linux team: %s' % team.name)
        data = self._add_common(team)
        logger.debug('team data: %s' % data)
        self.linuxteam_data[team.name] = data
        if team.routes:
            self._add_routes(team.name, team.routes)

    def add_ovs_tunnel(self, tunnel):
        """Add a OvsTunnel object to the net config object.

        :param tunnel: The OvsTunnel object to add.
        """
        logger.info('adding ovs tunnel: %s' % tunnel.name)
        data = self._add_common(tunnel)
        logger.debug('ovs tunnel data: %s' % data)
        self.interface_data[tunnel.name] = data

    def add_ovs_patch_port(self, ovs_patch_port):
        """Add a OvsPatchPort object to the net config object.

        :param ovs_patch_port: The OvsPatchPort object to add.
        """
        logger.info('adding ovs patch port: %s' % ovs_patch_port.name)
        data = self._add_common(ovs_patch_port)
        logger.debug('ovs patch port data: %s' % data)
        self.interface_data[ovs_patch_port.name] = data

    def add_ib_interface(self, ib_interface):
        """Add an InfiniBand interface object to the net config object.

        :param ib_interface: The InfiniBand interface object to add.
        """
        logger.info('adding ib_interface: %s' % ib_interface.name)
        data = self._add_common(ib_interface)
        logger.debug('ib_interface data: %s' % data)
        self.ib_interface_data[ib_interface.name] = data
        if ib_interface.routes:
            self._add_routes(ib_interface.name, ib_interface.routes)

        if ib_interface.renamed:
            logger.info("InfiniBand interface %s being renamed to %s"
                        % (ib_interface.hwname, ib_interface.name))
            self.renamed_interfaces[ib_interface.hwname] = ib_interface.name

    def add_ovs_dpdk_port(self, ovs_dpdk_port):
        """Add a OvsDpdkPort object to the net config object.

        :param ovs_dpdk_port: The OvsDpdkPort object to add.
        """
        logger.info('adding ovs dpdk port: %s' % ovs_dpdk_port.name)

        # DPDK Port will have only one member of type Interface, validation
        # checks are added at the object creation stage.
        ifname = ovs_dpdk_port.members[0].name

        # Bind the dpdk interface
        utils.bind_dpdk_interfaces(ifname, ovs_dpdk_port.driver, self.noop)
        if not self.noop:
            remove_ifcfg_config(ifname)

        data = self._add_common(ovs_dpdk_port)
        logger.debug('ovs dpdk port data: %s' % data)
        self.interface_data[ovs_dpdk_port.name] = data

    def add_ovs_dpdk_bond(self, ovs_dpdk_bond):
        """Add an OvsDPDKBond object to the net config object.

        :param ovs_dpdk_bond: The OvsBond object to add.
        """
        logger.info('adding ovs dpdk bond: %s' % ovs_dpdk_bond.name)

        # Bind the dpdk interface
        for dpdk_port in ovs_dpdk_bond.members:
            # DPDK Port will have only one member of type Interface, validation
            # checks are added at the object creation stage.
            ifname = dpdk_port.members[0].name
            utils.bind_dpdk_interfaces(ifname, dpdk_port.driver, self.noop)
            if not self.noop:
                remove_ifcfg_config(ifname)

        data = self._add_common(ovs_dpdk_bond)
        logger.debug('ovs dpdk bond data: %s' % data)
        self.interface_data[ovs_dpdk_bond.name] = data
        if ovs_dpdk_bond.routes:
            self._add_routes(ovs_dpdk_bond.name, ovs_dpdk_bond.routes)

    def add_sriov_pf(self, sriov_pf):
        """Add a SriovPF object to the net config object

        :param sriov_pf: The SriovPF object to add
        """
        logger.info('adding sriov pf: %s' % sriov_pf.name)
        data = self._add_common(sriov_pf)
        logger.debug('sriov pf data: %s' % data)
        utils.update_sriov_pf_map(sriov_pf.name, sriov_pf.numvfs,
                                  self.noop, promisc=sriov_pf.promisc)
        self.interface_data[sriov_pf.name] = data

    def add_sriov_vf(self, sriov_vf):
        """Add a SriovVF object to the net config object

        :param sriov_vf: The SriovVF object to add
        """
        logger.info('adding sriov vf: %s for pf: %s, vfid: %d'
                    % (sriov_vf.name, sriov_vf.device, sriov_vf.vfid))
        data = self._add_common(sriov_vf)
        logger.debug('sriov vf data: %s' % data)
        self.interface_data[sriov_vf.name] = data
        if sriov_vf.routes:
            self._add_routes(sriov_vf.name, sriov_vf.routes)

    def add_vpp_interface(self, vpp_interface):
        """Add a VppInterface object to the net config object

        :param vpp_interface: The VppInterface object to add
        """
        vpp_interface.pci_dev = utils.get_pci_address(vpp_interface.name,
                                                      False)
        if not vpp_interface.pci_dev:
            vpp_interface.pci_dev = utils.get_stored_pci_address(
                vpp_interface.name, False)
        vpp_interface.hwaddr = utils.interface_mac(vpp_interface.name)
        if not self.noop:
            self.ifdown(vpp_interface.name)
            remove_ifcfg_config(vpp_interface.name)
        logger.info('adding vpp interface: %s %s'
                    % (vpp_interface.name, vpp_interface.pci_dev))
        self.vpp_interface_data[vpp_interface.name] = vpp_interface

    def add_vpp_bond(self, vpp_bond):
        """Add a VppInterface object to the net config object

        :param vpp_bond: The VPPBond object to add
        """
        logger.info('adding vpp bond: %s' % vpp_bond.name)
        self.vpp_bond_data[vpp_bond.name] = vpp_bond

    def add_contrail_vrouter(self, contrail_vrouter):
        """Add a ContraiVrouter object to the net config object

        :param contrail_vrouter:
           The ContrailVrouter object to add
        """
        logger.info('adding contrail_vrouter interface: %s'
                    % contrail_vrouter.name)
        # Contrail vrouter will have the only member (of type interface,
        # vlan or linux_bond)
        ifname = contrail_vrouter.members[0].name
        data = self._add_common(contrail_vrouter)
        data += "DEVICETYPE=vhost\n"
        data += "TYPE=kernel_mode\n"
        data += "BIND_INT=%s\n" % ifname
        logger.debug('contrail data: %s' % data)
        self.interface_data[contrail_vrouter.name] = data
        if contrail_vrouter.routes:
            self._add_routes(contrail_vrouter.name,
                             contrail_vrouter.routes)

    def add_contrail_vrouter_dpdk(self, contrail_vrouter_dpdk):
        """Add a ContraiVrouterDpdk object to the net config object

        :param contrail_vrouter_dpdk:
           The ContrailVrouterDpdk object to add
        """
        logger.info('adding contrail vrouter dpdk interface: %s'
                    % contrail_vrouter_dpdk.name)
        pci_string = ",".join(
            utils.translate_ifname_to_pci_address(bind_int.name, self.noop)
            for bind_int in contrail_vrouter_dpdk.members)
        data = self._add_common(contrail_vrouter_dpdk)
        data += "DEVICETYPE=vhost\n"
        data += "TYPE=dpdk\n"
        data += "BIND_INT=%s\n" % pci_string
        if len(contrail_vrouter_dpdk.members) > 1:
            data += "BOND_MODE=%s\n" % contrail_vrouter_dpdk.bond_mode
            data += "BOND_POLICY=%s\n" % contrail_vrouter_dpdk.bond_policy
        data += "DRIVER=%s\n" % contrail_vrouter_dpdk.driver
        data += "CPU_LIST=%s\n" % contrail_vrouter_dpdk.cpu_list
        if contrail_vrouter_dpdk.vlan_id:
            data += "VLAN_ID=%s\n" % contrail_vrouter_dpdk.vlan_id
        logger.debug('contrail dpdk data: %s' % data)
        self.interface_data[contrail_vrouter_dpdk.name] = data
        if contrail_vrouter_dpdk.routes:
            self._add_routes(contrail_vrouter_dpdk.name,
                             contrail_vrouter_dpdk.routes)

    def generate_ivs_config(self, ivs_uplinks, ivs_interfaces):
        """Generate configuration content for ivs."""

        intfs = []
        for intf in ivs_uplinks:
            intfs.append(' -u ')
            intfs.append(intf)
        uplink_str = ''.join(intfs)

        intfs = []
        for intf in ivs_interfaces:
            intfs.append(' --internal-port=')
            intfs.append(intf)
        intf_str = ''.join(intfs)

        data = ("DAEMON_ARGS=\"--hitless --certificate /etc/ivs "
                "--inband-vlan 4092%s%s\""
                % (uplink_str, intf_str))
        return data

    def generate_nfvswitch_config(self, nfvswitch_ifaces,
                                  nfvswitch_internal_ifaces):
        """Generate configuration content for nfvswitch."""

        options_str = ""
        if self.nfvswitch_options:
            options_str = self.nfvswitch_options

        ifaces = []
        for iface in nfvswitch_ifaces:
            ifaces.append(' -u ')
            ifaces.append(iface)
        iface_str = ''.join(ifaces)

        ifaces = []
        for iface in nfvswitch_internal_ifaces:
            ifaces.append(' -m ')
            ifaces.append(iface)
        internal_str = ''.join(ifaces)

        data = "SETUP_ARGS=\"%s%s%s\"" % (options_str, iface_str, internal_str)
        return data

    def apply(self, cleanup=False, activate=True):
        """Apply the network configuration.

        :param cleanup: A boolean which indicates whether any undefined
            (existing but not present in the object model) interface
            should be disabled and deleted.
        :param activate: A boolean which indicates if the config should
            be activated by stopping/starting interfaces
            NOTE: if cleanup is specified we will deactivate interfaces even
            if activate is false
        :returns: a dict of the format: filename/data which contains info
            for each file that was changed (or would be changed if in --noop
            mode).
        Note the noop mode is set via the constructor noop boolean
        """
        logger.info('applying network configs...')
        restart_interfaces = []
        restart_vlans = []
        restart_bridges = []
        restart_linux_bonds = []
        restart_linux_teams = []
        restart_vpp = False
        update_files = {}
        all_file_names = []
        ivs_uplinks = []  # ivs physical uplinks
        ivs_interfaces = []  # ivs internal ports
        nfvswitch_interfaces = []       # nfvswitch physical interfaces
        nfvswitch_internal_ifaces = []  # nfvswitch internal/management ports
        stop_dhclient_interfaces = []
        ovs_needs_restart = False
        vpp_interfaces = self.vpp_interface_data.values()
        vpp_bonds = self.vpp_bond_data.values()

        for interface_name, iface_data in self.interface_data.items():
            route_data = self.route_data.get(interface_name, '')
            route6_data = self.route6_data.get(interface_name, '')
            interface_path = self.root_dir + ifcfg_config_path(interface_name)
            route_path = self.root_dir + route_config_path(interface_name)
            route6_path = self.root_dir + route6_config_path(interface_name)
            all_file_names.append(interface_path)
            all_file_names.append(route_path)
            all_file_names.append(route6_path)
            if "IVS_BRIDGE" in iface_data:
                ivs_uplinks.append(interface_name)
            if "NFVSWITCH_BRIDGE" in iface_data:
                nfvswitch_interfaces.append(interface_name)
            all_file_names.append(route6_path)
            if (utils.diff(interface_path, iface_data) or
                    utils.diff(route_path, route_data) or
                    utils.diff(route6_path, route6_data)):
                restart_interfaces.append(interface_name)
                restart_interfaces.extend(self.child_members(interface_name))
                update_files[interface_path] = iface_data
                update_files[route_path] = route_data
                update_files[route6_path] = route6_data
                if "BOOTPROTO=dhcp" not in iface_data:
                    stop_dhclient_interfaces.append(interface_name)
                # Openvswitch needs to be restarted when OVSDPDKPort or
                # OVSDPDKBond is added
                if "OVSDPDK" in iface_data:
                    ovs_needs_restart = True

            else:
                logger.info('No changes required for interface: %s' %
                            interface_name)

        for interface_name, iface_data in self.ivsinterface_data.items():
            route_data = self.route_data.get(interface_name, '')
            route6_data = self.route6_data.get(interface_name, '')
            interface_path = self.root_dir + ifcfg_config_path(interface_name)
            route_path = self.root_dir + route_config_path(interface_name)
            route6_path = self.root_dir + route6_config_path(interface_name)
            all_file_names.append(interface_path)
            all_file_names.append(route_path)
            all_file_names.append(route6_path)
            ivs_interfaces.append(interface_name)
            if (utils.diff(interface_path, iface_data) or
                    utils.diff(route_path, route_data)):
                restart_interfaces.append(interface_name)
                restart_interfaces.extend(self.child_members(interface_name))
                update_files[interface_path] = iface_data
                update_files[route_path] = route_data
                update_files[route6_path] = route6_data
            else:
                logger.info('No changes required for ivs interface: %s' %
                            interface_name)

        for iface_name, iface_data in self.nfvswitch_intiface_data.items():
            route_data = self.route_data.get(iface_name, '')
            route6_data = self.route6_data.get(iface_name, '')
            iface_path = self.root_dir + ifcfg_config_path(iface_name)
            route_path = self.root_dir + route_config_path(iface_name)
            route6_path = self.root_dir + route6_config_path(iface_name)
            all_file_names.append(iface_path)
            all_file_names.append(route_path)
            all_file_names.append(route6_path)
            nfvswitch_internal_ifaces.append(iface_name)
            if (utils.diff(iface_path, iface_data) or
                    utils.diff(route_path, route_data)):
                restart_interfaces.append(iface_name)
                restart_interfaces.extend(self.child_members(iface_name))
                update_files[iface_path] = iface_data
                update_files[route_path] = route_data
                update_files[route6_path] = route6_data
            else:
                logger.info('No changes required for nfvswitch interface: %s' %
                            iface_name)

        for vlan_name, vlan_data in self.vlan_data.items():
            route_data = self.route_data.get(vlan_name, '')
            route6_data = self.route6_data.get(vlan_name, '')
            vlan_path = self.root_dir + ifcfg_config_path(vlan_name)
            vlan_route_path = self.root_dir + route_config_path(vlan_name)
            vlan_route6_path = self.root_dir + route6_config_path(vlan_name)
            all_file_names.append(vlan_path)
            all_file_names.append(vlan_route_path)
            all_file_names.append(vlan_route6_path)
            if (utils.diff(vlan_path, vlan_data) or
                    utils.diff(vlan_route_path, route_data)):
                restart_vlans.append(vlan_name)
                restart_vlans.extend(self.child_members(vlan_name))
                update_files[vlan_path] = vlan_data
                update_files[vlan_route_path] = route_data
                update_files[vlan_route6_path] = route6_data
            else:
                logger.info('No changes required for vlan interface: %s' %
                            vlan_name)

        for bridge_name, bridge_data in self.bridge_data.items():
            route_data = self.route_data.get(bridge_name, '')
            route6_data = self.route6_data.get(bridge_name, '')
            bridge_path = self.root_dir + bridge_config_path(bridge_name)
            br_route_path = self.root_dir + route_config_path(bridge_name)
            br_route6_path = self.root_dir + route6_config_path(bridge_name)
            all_file_names.append(bridge_path)
            all_file_names.append(br_route_path)
            all_file_names.append(br_route6_path)
            if (utils.diff(bridge_path, bridge_data) or
                    utils.diff(br_route_path, route_data) or
                    utils.diff(br_route6_path, route6_data)):
                restart_bridges.append(bridge_name)
                # Avoid duplicate interface being added to the restart list
                children = self.child_members(bridge_name)
                for child in children:
                    if child not in restart_interfaces:
                        restart_interfaces.append(child)
                update_files[bridge_path] = bridge_data
                update_files[br_route_path] = route_data
                update_files[br_route6_path] = route6_data
            else:
                logger.info('No changes required for bridge: %s' % bridge_name)

        for bridge_name, bridge_data in self.linuxbridge_data.items():
            route_data = self.route_data.get(bridge_name, '')
            route6_data = self.route6_data.get(bridge_name, '')
            bridge_path = self.root_dir + bridge_config_path(bridge_name)
            br_route_path = self.root_dir + route_config_path(bridge_name)
            br_route6_path = self.root_dir + route6_config_path(bridge_name)
            all_file_names.append(bridge_path)
            all_file_names.append(br_route_path)
            all_file_names.append(br_route6_path)
            if (utils.diff(bridge_path, bridge_data) or
                    utils.diff(br_route_path, route_data) or
                    utils.diff(br_route6_path, route6_data)):
                restart_bridges.append(bridge_name)
                restart_interfaces.extend(self.child_members(bridge_name))
                update_files[bridge_path] = bridge_data
                update_files[br_route_path] = route_data
                update_files[br_route6_path] = route6_data
            else:
                logger.info('No changes required for bridge: %s' % bridge_name)

        for team_name, team_data in self.linuxteam_data.items():
            route_data = self.route_data.get(team_name, '')
            route6_data = self.route6_data.get(team_name, '')
            team_path = self.root_dir + bridge_config_path(team_name)
            team_route_path = self.root_dir + route_config_path(team_name)
            team_route6_path = self.root_dir + route6_config_path(team_name)
            all_file_names.append(team_path)
            all_file_names.append(team_route_path)
            all_file_names.append(team_route6_path)
            if (utils.diff(team_path, team_data) or
                    utils.diff(team_route_path, route_data) or
                    utils.diff(team_route6_path, route6_data)):
                restart_linux_teams.append(team_name)
                restart_interfaces.extend(self.child_members(team_name))
                update_files[team_path] = team_data
                update_files[team_route_path] = route_data
                update_files[team_route6_path] = route6_data
            else:
                logger.info('No changes required for linux team: %s' %
                            team_name)

        for bond_name, bond_data in self.linuxbond_data.items():
            route_data = self.route_data.get(bond_name, '')
            route6_data = self.route6_data.get(bond_name, '')
            bond_path = self.root_dir + bridge_config_path(bond_name)
            bond_route_path = self.root_dir + route_config_path(bond_name)
            bond_route6_path = self.root_dir + route6_config_path(bond_name)
            all_file_names.append(bond_path)
            all_file_names.append(bond_route_path)
            all_file_names.append(bond_route6_path)
            if (utils.diff(bond_path, bond_data) or
                    utils.diff(bond_route_path, route_data) or
                    utils.diff(bond_route6_path, route6_data)):
                restart_linux_bonds.append(bond_name)
                restart_interfaces.extend(self.child_members(bond_name))
                update_files[bond_path] = bond_data
                update_files[bond_route_path] = route_data
                update_files[bond_route6_path] = route6_data
            else:
                logger.info('No changes required for linux bond: %s' %
                            bond_name)

        # Infiniband interfaces are handled similarly to Ethernet interfaces
        for interface_name, iface_data in self.ib_interface_data.items():
            route_data = self.route_data.get(interface_name, '')
            route6_data = self.route6_data.get(interface_name, '')
            interface_path = self.root_dir + ifcfg_config_path(interface_name)
            route_path = self.root_dir + route_config_path(interface_name)
            route6_path = self.root_dir + route6_config_path(interface_name)
            all_file_names.append(interface_path)
            all_file_names.append(route_path)
            all_file_names.append(route6_path)
            # TODO(dsneddon) determine if InfiniBand can be used with IVS
            if "IVS_BRIDGE" in iface_data:
                ivs_uplinks.append(interface_name)
            all_file_names.append(route6_path)
            if (utils.diff(interface_path, iface_data) or
                    utils.diff(route_path, route_data) or
                    utils.diff(route6_path, route6_data)):
                restart_interfaces.append(interface_name)
                restart_interfaces.extend(self.child_members(interface_name))
                update_files[interface_path] = iface_data
                update_files[route_path] = route_data
                update_files[route6_path] = route6_data
            else:
                logger.info('No changes required for InfiniBand iface: %s' %
                            interface_name)

        if self.vpp_interface_data or self.vpp_bond_data:
            vpp_path = self.root_dir + vpp_config_path()
            vpp_config = utils.generate_vpp_config(vpp_path, vpp_interfaces,
                                                   vpp_bonds)
            if utils.diff(vpp_path, vpp_config):
                restart_vpp = True
                update_files[vpp_path] = vpp_config
            else:
                logger.info('No changes required for VPP')

        if cleanup:
            for ifcfg_file in glob.iglob(cleanup_pattern()):
                if ifcfg_file not in all_file_names:
                    interface_name = ifcfg_file[len(cleanup_pattern()) - 1:]
                    if interface_name != 'lo':
                        logger.info('cleaning up interface: %s'
                                    % interface_name)
                        self.ifdown(interface_name)
                        self.remove_config(ifcfg_file)

        if activate:
            for vlan in restart_vlans:
                self.ifdown(vlan)

            for interface in restart_interfaces:
                self.ifdown(interface)

            for linux_bond in restart_linux_bonds:
                self.ifdown(linux_bond)

            for linux_team in restart_linux_teams:
                self.ifdown(linux_team)

            for bridge in restart_bridges:
                self.ifdown(bridge, iftype='bridge')

            for vpp_interface in vpp_interfaces:
                self.ifdown(vpp_interface.name)

            for oldname, newname in self.renamed_interfaces.items():
                self.ifrename(oldname, newname)

            # DPDK initialization is done before running os-net-config, to make
            # the DPDK ports available when enabled. DPDK Hotplug support is
            # supported only in OvS 2.7 version. Until then, OvS needs to be
            # restarted after adding a DPDK port. This change will be removed
            # on migration to OvS 2.7 where DPDK Hotplug support is available.
            if ovs_needs_restart:
                msg = "Restart openvswitch"
                self.execute(msg, '/usr/bin/systemctl',
                             'restart', 'openvswitch')

        for location, data in update_files.items():
            self.write_config(location, data)

        if ivs_uplinks or ivs_interfaces:
            location = ivs_config_path()
            data = self.generate_ivs_config(ivs_uplinks, ivs_interfaces)
            if (utils.diff(location, data)):
                self.write_config(location, data)
                msg = "Restart ivs"
                self.execute(msg, '/usr/bin/systemctl',
                             'restart', 'ivs')

        if nfvswitch_interfaces or nfvswitch_internal_ifaces:
            location = nfvswitch_config_path()
            data = self.generate_nfvswitch_config(nfvswitch_interfaces,
                                                  nfvswitch_internal_ifaces)
            if (utils.diff(location, data)):
                self.write_config(location, data)
                msg = "Restart nfvswitch"
                self.execute(msg, '/usr/bin/systemctl',
                             'restart', 'nfvswitch')

        if activate:
            for linux_team in restart_linux_teams:
                self.ifup(linux_team)

            for bridge in restart_bridges:
                self.ifup(bridge, iftype='bridge')

            # If dhclient is running and dhcp not set, stop dhclient
            for interface in stop_dhclient_interfaces:
                logger.debug("Calling stop_dhclient_interfaces() for %s" %
                             interface)
                if not self.noop:
                    stop_dhclient_process(interface)

            for interface in restart_interfaces:
                self.ifup(interface)

            for linux_bond in restart_linux_bonds:
                self.ifup(linux_bond)

            for bond in self.bond_primary_ifaces:
                self.ovs_appctl('bond/set-active-slave', bond,
                                self.bond_primary_ifaces[bond])

            if ivs_uplinks or ivs_interfaces:
                logger.info("Attach to ivs with "
                            "uplinks: %s, "
                            "interfaces: %s" %
                            (ivs_uplinks, ivs_interfaces))
                for ivs_uplink in ivs_uplinks:
                    self.ifup(ivs_uplink)
                for ivs_interface in ivs_interfaces:
                    self.ifup(ivs_interface)

            if nfvswitch_interfaces or nfvswitch_internal_ifaces:
                logger.info("Attach to nfvswitch with "
                            "interfaces: %s, "
                            "internal interfaces: %s" %
                            (nfvswitch_interfaces, nfvswitch_internal_ifaces))
                for nfvswitch_interface in nfvswitch_interfaces:
                    self.ifup(nfvswitch_interface)
                for nfvswitch_internal in nfvswitch_internal_ifaces:
                    self.ifup(nfvswitch_internal)

            for vlan in restart_vlans:
                self.ifup(vlan)

            if not self.noop:
                if restart_vpp:
                    logger.info('Restarting VPP')
                    utils.restart_vpp(vpp_interfaces)

                if self.vpp_interface_data:
                    logger.info('Updating VPP mapping')
                    utils.update_vpp_mapping(vpp_interfaces, vpp_bonds)

            if self.errors:
                message = 'Failure(s) occurred when applying configuration'
                logger.error(message)
                for e in self.errors:
                    logger.error('stdout: %s, stderr: %s', e.stdout, e.stderr)
                raise os_net_config.ConfigurationError(message)

        return update_files
