#! /usr/bin/python
#
# Copyright 2011-2014 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Refer to the README and COPYING files for full details of the license
#
import argparse
import logging
import logging.config
import os

from vdsm.config import config
from vdsm import netinfo
from vdsm.constants import P_VDSM_RUN

# Ifcfg persistence restoration
from network.configurators import ifcfg

# Unified persistence restoration
from network.api import setupNetworks
from network import configurators
from vdsm.netconfpersistence import RunningConfig, PersistentConfig
import pkgutil

_NETS_RESTORED_MARK = os.path.join(P_VDSM_RUN, 'nets_restored')


def ifcfg_restoration():
    configWriter = ifcfg.ConfigWriter()
    configWriter.restorePersistentBackup()


def unified_restoration():
    """
    Builds a setupNetworks command from the persistent configuration to set it
    as running configuration.
    """
    runningConfig = RunningConfig()
    removeNetworks = {}
    removeBonds = {}
    for network in runningConfig.networks:
        removeNetworks[network] = {'remove': True}
    for bond in runningConfig.bonds:
        removeBonds[bond] = {'remove': True}
    logging.debug('Removing all networks (%s) and bonds (%s) in running '
                  'config.', removeNetworks, removeBonds)
    setupNetworks(removeNetworks, removeBonds, connectivityCheck=False,
                  _inRollback=True)

    # Flush vdsm configurations left-overs from any configurator on the system
    # so that changes of configurator and persistence system are smooth.
    for configurator_cls in _get_all_configurators():
        configurator_cls().flush()

    # Restore non-VDSM network devices (BZ#1188251)
    configWriter = ifcfg.ConfigWriter()
    configWriter.restorePersistentBackup()

    persistentConfig = PersistentConfig()
    nets, bonds = _filter_nets_bonds(persistentConfig.networks,
                                     persistentConfig.bonds)
    logging.debug('Calling setupNetworks with networks (%s) and bond (%s).',
                  nets, bonds)
    setupNetworks(nets, bonds, connectivityCheck=False, _inRollback=True)


def _filter_nets_bonds(nets, bonds):
    """Returns only nets and bonds that can be configured with the devices
    present in the system"""
    available_nets, available_bonds = {}, {}
    available_nics = netinfo.nics()
    for bond, attrs in bonds.iteritems():
        available_bond_nics = [nic for nic in attrs['nics'] if
                               nic in available_nics]
        if available_bond_nics:
            available_bonds[bond] = attrs.copy()
            available_bonds[bond]['nics'] = available_bond_nics

    for net, attrs in nets.iteritems():
        bond = attrs.get('bonding')
        if bond is not None:
            if bond not in bonds:
                logging.error('Bond "%s" is not configured. '
                              'Network "%s" will not be '
                              'configured as a consequence', bond, net)
            elif bond not in available_bonds:
                logging.error('Some of the nics required by bond "%s" (%s) '
                              'are missing. Network "%s" will not be '
                              'configured as a consequence', bond,
                              bonds[bond]['nics'], net)
            else:
                available_nets[net] = attrs
            continue  # Regardless of availability, the net is processed

        nic = attrs.get('nic')
        if nic is not None:
            if nic not in available_nics:
                logging.error('Nic "%s" required by network %s is missing. '
                              'The network will not be configured', nic, net)
            else:
                available_nets[net] = attrs
            continue  # Regardless of availability, the net is processed

        # Bridge-only nics
        available_nets[net] = attrs
    return available_nets, available_bonds


def _get_all_configurators():
    """Returns the class objects of all the configurators in the netconf pkg"""
    prefix = configurators.__name__ + '.'
    for importer, moduleName, isPackage in pkgutil.iter_modules(
            configurators.__path__, prefix):
        __import__(moduleName, fromlist="_")

    for cls in configurators.Configurator.__subclasses__():
        yield cls


def _nets_already_restored(nets_restored_mark):
    return os.path.exists(nets_restored_mark)


def touch_file(file_path):
    with open(file_path, 'a'):
        os.utime(file_path, None)


def restore(args):
    if not args.force and _nets_already_restored(_NETS_RESTORED_MARK):
        logging.info('networks already restored. doing nothing.')
        return

    if config.get('vars', 'net_persistence') == 'unified':
        unified_restoration()
    else:
        ifcfg_restoration()

    touch_file(_NETS_RESTORED_MARK)


if __name__ == '__main__':
    try:
        logging.config.fileConfig('/etc/vdsm/svdsm.logger.conf',
                                  disable_existing_loggers=False)
    except:
        logging.basicConfig(filename='/dev/stdout', filemode='w+',
                            level=logging.DEBUG)
        logging.error('Could not init proper logging', exc_info=True)

    restore_help = ("Restores the network configuration from vdsm configured "
                    "network system persistence.\n"
                    "Restoration will delete any trace of network system "
                    "persistence except the vdsm internal persistent network "
                    "configuration. In order to avoid this use --no-flush.")
    parser = argparse.ArgumentParser(description=restore_help)

    force_option_help = ("Restore networks even if the " + _NETS_RESTORED_MARK
                         + " mark exists. The mark is created upon a previous "
                           "successful restore")
    parser.add_argument('--force', action='store_true', default=False,
                        help=force_option_help)

    args = parser.parse_args()
    restore(args)
