#    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 sys

from oslo_log import log as logging

from vif_plug_ovs import constants
from vif_plug_ovs import linux_net
from vif_plug_ovs.ovsdb import api as ovsdb_api


LOG = logging.getLogger(__name__)


class BaseOVS(object):

    def __init__(self, config):
        self.timeout = config.ovs_vsctl_timeout
        self.connection = config.ovsdb_connection
        self.interface = config.ovsdb_interface
        self.ovsdb = ovsdb_api.get_instance(self)

    def _ovs_supports_mtu_requests(self):
        return self.ovsdb.has_table_column('Interface', 'mtu_request')

    def _set_mtu_request(self, dev, mtu):
        self.ovsdb.db_set('Interface', dev, ('mtu_request', mtu)).execute()

    def update_device_mtu(self, dev, mtu, interface_type=None):
        if not mtu:
            return
        if interface_type not in [
            constants.OVS_VHOSTUSER_INTERFACE_TYPE,
            constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE]:
            if sys.platform != constants.PLATFORM_WIN32:
                # Hyper-V with OVS does not support external programming of
                # virtual interface MTUs via netsh or other Windows tools.
                # When plugging an interface on Windows, we therefore skip
                # programming the MTU and fallback to DHCP advertisement.
                linux_net.set_device_mtu(dev, mtu)
        elif self._ovs_supports_mtu_requests():
            self._set_mtu_request(dev, mtu)
        else:
            LOG.debug("MTU not set on %(interface_name)s interface "
                      "of type %(interface_type)s.",
                      {'interface_name': dev,
                       'interface_type': interface_type})

    def ensure_ovs_bridge(self, bridge, datapath_type):
        return self.ovsdb.add_br(bridge, may_exist=True,
                                 datapath_type=datapath_type).execute()

    def create_ovs_vif_port(self, bridge, dev, iface_id, mac, instance_id,
                            mtu=None, interface_type=None,
                            vhost_server_path=None, tag=None,
                            pf_pci=None, vf_num=None):
        """Create OVS port

        :param bridge: bridge name to create the port on.
        :param dev: port name.
        :param iface_id: port ID.
        :param mac: port MAC.
        :param instance_id: VM ID on which the port is attached to.
        :param mtu: port MTU.
        :param interface_type: OVS interface type.
        :param vhost_server_path: path to socket file of vhost server.
        :param tag: OVS interface tag.
        :param pf_pci: PCI address of PF for dpdk representor port.
        :param vf_num: VF number of PF for dpdk representor port.

        .. note:: create DPDK representor port by setting all three values:
            `interface_type`, `pf_pci` and `vf_num`. if interface type is
            not `OVS_DPDK_INTERFACE_TYPE` then `pf_pci` and `vf_num` values
            are ignored.
        """
        external_ids = {'iface-id': iface_id,
                        'iface-status': 'active',
                        'attached-mac': mac,
                        'vm-uuid': instance_id}
        col_values = [('external_ids', external_ids)]
        if interface_type:
            col_values.append(('type', interface_type))
        if vhost_server_path:
            col_values.append(('options',
                               {'vhost-server-path': vhost_server_path}))
        if (interface_type == constants.OVS_DPDK_INTERFACE_TYPE and
                pf_pci and vf_num):
            devargs_string = "{PF_PCI},representor=[{VF_NUM}]".format(
                PF_PCI=pf_pci, VF_NUM=vf_num)
            col_values.append(('options',
                              {'dpdk-devargs': devargs_string}))
        with self.ovsdb.transaction() as txn:
            txn.add(self.ovsdb.add_port(bridge, dev))
            if tag:
                txn.add(self.ovsdb.db_set('Port', dev, ('tag', tag)))
            txn.add(self.ovsdb.db_set('Interface', dev, *col_values))
        self.update_device_mtu(dev, mtu, interface_type=interface_type)

    def update_ovs_vif_port(self, dev, mtu=None, interface_type=None):
        self.update_device_mtu(dev, mtu, interface_type=interface_type)

    def delete_ovs_vif_port(self, bridge, dev, delete_netdev=True):
        self.ovsdb.del_port(dev, bridge=bridge, if_exists=True).execute()
        if delete_netdev:
            linux_net.delete_net_dev(dev)
