
from __future__ import absolute_import
from __future__ import division

import os
import shutil
import string

import six

from vdsm.common.config import config

from vdsm.network.canonicalize import canonicalize_bondings
from vdsm.network.canonicalize import canonicalize_networks
from vdsm.network.configurators.ifcfg import ConfigWriter
from vdsm.network.configurators.ifcfg import Ifcfg
from vdsm.network.kernelconfig import KernelConfig
from vdsm.network.link import sriov
from vdsm.network.netconfpersistence import RunningConfig, PersistentConfig
from vdsm.network.netinfo.cache import NetInfo, libvirt_vdsm_nets
from vdsm.network.netswitch.configurator import netinfo
from vdsm.network.ovs import info as ovs_info
from vdsm.network.ovs import switch as ovs_switch


try:
    from vdsm.virt import libvirtnetwork
except ImportError:
    class LibvirtNetworkMock(object):
        def networks(self):
            return {}

        def removeNetwork(self, net):
            pass

    libvirtnetwork = LibvirtNetworkMock()


def upgrade():
    rconfig = RunningConfig()
    pconfig = PersistentConfig()

    libvirt_networks = libvirtnetwork.networks()

    _upgrade_volatile_running_config(rconfig)

    if rconfig.config_exists() or pconfig.config_exists():
        _upgrade_unified_configuration(rconfig)
        _upgrade_unified_configuration(pconfig)
    else:
        vdsmnets = libvirt_vdsm_nets(libvirt_networks)
        _create_unified_configuration(rconfig, NetInfo(netinfo(vdsmnets)))

    _cleanup_libvirt_networks(libvirt_networks)

    if ovs_info.is_ovs_service_running():
        ovs_switch.update_network_to_bridge_mappings(ovs_info.OvsInfo())


def _upgrade_volatile_running_config(rconfig):
    if not rconfig.config_exists():
        volatile_rconfig = RunningConfig(volatile=True)
        if volatile_rconfig.config_exists():
            rconfig.networks = volatile_rconfig.networks
            rconfig.bonds = volatile_rconfig.bonds
            rconfig.save()
            volatile_rconfig.delete()


def _upgrade_unified_configuration(config):
    save_changes = False

    if config.networks:
        _normalize_net_address(config.networks)
        _normalize_net_ifcfg_keys(config.networks)

        canonicalize_networks(config.networks)
        canonicalize_bondings(config.bonds)

        save_changes = True

    old_sriov_confpath = os.path.join(config.netconf_path, 'virtual_functions')
    if os.path.exists(old_sriov_confpath):
        _upgrade_sriov_config(config.devices, old_sriov_confpath)
        save_changes = True

    if save_changes:
        config.save()


def _normalize_net_address(networks):
    for net_name, net_attr in six.viewitems(networks):
        if 'defaultRoute' not in net_attr:
            net_attr['defaultRoute'] = net_name in ('ovirtmgmt', 'rhevm')


def _normalize_net_ifcfg_keys(networks):
    for netname, netattrs in six.viewitems(networks):
        networks[netname] = {k: v for k, v in six.viewitems(netattrs)
                             if not _is_unsupported_ifcfg_key(k)}


def _upgrade_sriov_config(devices, old_sriov_confpath):

    old_config = sriov.get_old_persisted_devices_numvfs(old_sriov_confpath)
    new_config = sriov.upgrade_devices_sriov_config(old_config)

    for devname in new_config:
        devices.setdefault(devname, {}).update(new_config[devname])

    shutil.rmtree(old_sriov_confpath)


def _is_unsupported_ifcfg_key(key):
    return set(key) <= set(string.ascii_uppercase + string.digits + '_')


def _create_unified_configuration(rconfig, net_info):
    kconfig = KernelConfig(net_info)

    rconfig.networks = kconfig.networks
    rconfig.bonds = _filter_owned_bonds(kconfig.bonds)

    rconfig.save()
    ConfigWriter.clearBackups()
    RunningConfig.store()


def _filter_owned_bonds(kconfig_bonds):
    if config.get('vars', 'net_persistence') == 'ifcfg':
        return {bond_name: bond_attrs
                for bond_name, bond_attrs in six.viewitems(kconfig_bonds)
                if Ifcfg.owned_device(bond_name)}
    return {}


def _cleanup_libvirt_networks(libvirt_networks):
    for net in libvirt_networks:
        libvirtnetwork.removeNetwork(net)
