# 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 os
import re

from kuryr.lib import binding
from kuryr.lib import exceptions

from kuryr_libnetwork import constants as const
from kuryr_libnetwork.port_driver import driver


def get_ifname_by_pci_address(pci_addr, pf_interface=False):
    """Get the interface name based on a VF's pci address.

    The returned interface name is either the parent PF's or that of the VF
    itself based on the argument of pf_interface.
    """
    dev_path = _get_sysfs_netdev_path(pci_addr, pf_interface)
    try:
        dev_info = os.listdir(dev_path)
        return dev_info.pop()
    except Exception:
        raise exceptions.KuryrException(
            "PCI device %s not found" % pci_addr)


def _get_sysfs_netdev_path(pci_addr, pf_interface):
    """Get the sysfs path based on the PCI address of the device.

    Assumes a networking device - will not check for the existence of the path.
    """
    if pf_interface:
        return "/sys/bus/pci/devices/%s/physfn/net" % pci_addr
    return "/sys/bus/pci/devices/%s/net" % pci_addr


def get_vf_num_by_pci_address(pci_addr):
    """Get the VF number based on a VF's pci address

    A VF is associated with an VF number, which ip link command uses to
    configure it. This number can be obtained from the PCI device filesystem.
    """
    VIRTFN_RE = re.compile(r"virtfn(\d+)")
    virtfns_path = "/sys/bus/pci/devices/%s/physfn/virtfn*" % (pci_addr)
    vf_num = None
    try:
        for vf_path in glob.iglob(virtfns_path):
            if re.search(pci_addr, os.readlink(vf_path)):
                t = VIRTFN_RE.search(vf_path)
                vf_num = t.group(1)
                break
    except Exception:
        pass
    if vf_num is None:
        raise exceptions.KuryrException(
            "PCI device %s not found" % pci_addr)
    return vf_num


class SriovDriver(driver.Driver):
    """Driver supporting SR-IOV on Bare Metal"""

    BINDING_DRIVERS = ('hw_veb',)

    def get_supported_bindings(self):
        """Returns a tuple of supported binding driver names for the driver.

        :returns: a tuple of strings
        """
        return self.BINDING_DRIVERS

    def get_default_network_id(self):
        """Returns a Neutron network ID as per driver logic, if any.

        This driver does not make use of any specific network and will thus
        return None.

        :returns: None
        """
        return None

    def create_host_iface(self, endpoint_id, neutron_port, subnets,
                          network=None):
        """Instantiates a host interface and bind it to the host.

        :param endpoint_id:  the ID of the endpoint as string
        :param neutron_port: the container Neutron port dictionary as returned
                             by python-neutronclient
        :param subnets:      an iterable of all the Neutron subnets which the
                             endpoint is trying to join
        :param network:      the Neutron network which the endpoint is trying
                             to join
        :returns: the tuple of stdout and stderr returned by
                  processutils.execute invoked with the executable script for
                  unbinding
        :raises: kuryr.lib.exceptions.BindingNotSupportedFailure
                 processutils.ProcessExecutionError
        """
        binding_driver = 'kuryr.lib.binding.drivers.hw_veb'
        pci_addr = neutron_port[const.BINDING_PROFILE]['pci_slot']
        pf_ifname = get_ifname_by_pci_address(pci_addr,
                                              pf_interface=True)
        vf_num = get_vf_num_by_pci_address(pci_addr)
        _, _, (stdout, stderr) = binding.port_bind(
            endpoint_id, neutron_port, subnets, pf_ifname=pf_ifname,
            vf_num=vf_num, driver=binding_driver)
        return (stdout, stderr)

    def delete_host_iface(self, endpoint_id, neutron_port):
        """Deletes a host interface after unbinding it from the host.

        The host veth interface associated to the Neutron port will be unbound
        from its vitual bridge and deleted by delegating to the selected
        kuryr-lib driver.

        :param endpoint_id:  the ID of the Docker container as string
        :param neutron_port: a port dictionary returned from
               python-neutronclient
        :returns: the tuple of stdout and stderr returned by
                  processutils.execute invoked with the executable script for
                  unbinding
        :raises: processutils.ProcessExecutionError
        """
        binding_driver = 'kuryr.lib.binding.drivers.hw_veb'
        pci_addr = neutron_port[const.BINDING_PROFILE]['pci_slot']
        pf_ifname = get_ifname_by_pci_address(pci_addr,
                                              pf_interface=True)
        vf_num = get_vf_num_by_pci_address(pci_addr)
        return binding.port_unbind(endpoint_id, neutron_port,
                                   pf_ifname=pf_ifname,
                                   vf_num=vf_num, driver=binding_driver)

    def get_container_iface_name(self, neutron_port):
        """Returns interface name of a container in the default namespace.

        :param neutron_port_id: The ID of a neutron port as string
        :returns: interface name as string
        """
        pci_addr = neutron_port[const.BINDING_PROFILE]['pci_slot']
        vf_ifname = get_ifname_by_pci_address(pci_addr)
        return vf_ifname
