#!/usr/bin/python3
#
# Copyright (c) 2018 Ultimum Technologies s.r.o.
#
# 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.
#
# Author: Michal Arbet <michal.arbet@ultimum.io>

from stevedore.named import ExtensionManager
import logging
from oslo_config import cfg
import fileinput
import sys
import re
import argparse
import os

CONF = cfg.CONF

# Logging
LOG = logging.getLogger()
LOG.setLevel(logging.INFO)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
LOG.addHandler(ch)

# Some neutron default paths
neutron_config_dir = "/etc/neutron/"
neutron_server_config_dir = "{}server.conf.d/".format(neutron_config_dir)
neutron_agent_config_dir = "{}agent.conf.d/".format(neutron_config_dir)
neutron_config_file_path = "{}neutron.conf".format(neutron_config_dir)

def restart_services():
    LOG.info("Restarting neutron services.")
    os.system("systemctl restart neutron-*.service")

def check_plugin(plugin):
    global plugins_dict
    plugin_exist = False
    for key in plugins_dict:
        if key == plugin:
            plugin_exist = True
    if not plugin_exist:
        LOG.error("Plugin {} is not neutron's service_plugin.".format(plugin))
        return False
    return True

def link_configs(plugin, restart=True):
    global plugins_dict
    if not check_plugin(plugin):
        return
    neutron_plugin = plugins_dict[plugin]['provides']
    module = plugins_dict[plugin]['provides'].split("_")[1]
    if neutron_plugin != "neutron":
        configs = os.listdir(neutron_config_dir)
        for cfg in configs:
            if module in cfg:
                if re.match(r".*\.conf$", cfg):
                    if not os.path.islink(neutron_server_config_dir + cfg):
                        os.symlink(neutron_config_dir + cfg, neutron_server_config_dir + cfg)
                        LOG.info("Creating symlink: {} -> {}".format(neutron_config_dir + cfg, neutron_server_config_dir + cfg))
                    else:
                        LOG.info("Symlink already exist: {} -> {}".format(neutron_config_dir + cfg, neutron_server_config_dir + cfg))
                if re.match(r".*\.ini$", cfg):
                    if not os.path.islink(neutron_agent_config_dir + cfg + ".conf"):
                        os.symlink(neutron_config_dir + cfg, neutron_agent_config_dir + cfg + ".conf")
                        LOG.info("Creating symlink: {} -> {}".format(neutron_config_dir + cfg,
                                                                     neutron_agent_config_dir + cfg + ".conf"))
                    else:
                        LOG.info("Symlink already exist: {} -> {}".format(neutron_config_dir + cfg, neutron_agent_config_dir + cfg + ".conf"))
    # Restart if requested
    if restart:
        restart_services()

def unlink_configs(plugin, restart=True):
    global plugins_dict
    if not check_plugin(plugin):
        return
    neutron_plugin = plugins_dict[plugin]['provides']
    module = plugins_dict[plugin]['provides'].split("_")[1]
    if neutron_plugin != "neutron":
        configs = os.listdir(neutron_config_dir)
        for cfg in configs:
            if module in cfg:
                if re.match(r".*\.conf$", cfg):
                    try:
                        os.remove(neutron_server_config_dir + cfg)
                        LOG.info(
                            "Removing symlink: {} -> {}".format(neutron_config_dir + cfg, neutron_server_config_dir + cfg))
                    except OSError:
                        pass
                if re.match(r".*\.ini$", cfg):
                    try:
                        os.remove(neutron_agent_config_dir + cfg + ".conf")
                        LOG.info("Removing symlink: {} -> {}".format(neutron_config_dir + cfg,
                                                                     neutron_agent_config_dir + cfg + ".conf"))
                    except OSError:
                        pass
    # Restart if requested
    if restart:
        restart_services()

# Replace line inplace
def replace(file, searchExp, replaceExp):
    uid = os.stat(file).st_uid
    gid = os.stat(file).st_gid
    config = fileinput.FileInput(file, inplace=True)
    for line in config:
        line = re.sub(searchExp, replaceExp, line.rstrip())
        print(line)
    os.chown(file,uid,gid)

def plugin_enable(plugin):
    global plugins_dict
    if not check_plugin(plugin):
        return
    neutron_plugin = plugins_dict[plugin]['provides']
    CONF(default_config_files=[neutron_config_file_path], args=[])
    plugins_enabled = CONF.service_plugins
    # Enable service_plugin of neutron-plugin package
    # only if some other service_plugin from that package not currently enabled
    plugins_to_test = []
    if neutron_plugin != "neutron":
        for k, v in plugins_dict.items():
            if v['provides'] == neutron_plugin:
                plugins_to_test.append(k)
                plugins_to_test.append(v['long_name'])
    else:
        plugins_to_test.append(plugin)
        plugins_to_test.append(plugins_dict[plugin]['long_name'])
    plugin_enable = False
    for i in plugins_to_test:
        if i in plugins_enabled:
            plugin_enable = False
            if plugin == i:
                LOG.error("Service plugin {} is already enabled.".format(plugin))
                restart = False
            else:
                LOG.error("Enabling service_plugin {} failed. Another service"
                          "plugin {} from {} is currently enabled.".format(plugin, i, neutron_plugin))
                restart = False
            break
        else:
            plugin_enable = True
    if plugin_enable:
        plugins_enabled.append(plugin)
        plugins_enabled = ",".join(plugins_enabled)
        LOG.info("Service plugin {} is now enabled.".format(plugin))
        replace(neutron_config_file_path, '^service_plugins.*', 'service_plugins = {}'.format(plugins_enabled))

def plugin_disable(plugin):
    global plugins_dict
    if not check_plugin(plugin):
        return
    CONF(default_config_files=[neutron_config_file_path], args=[])
    plugins_enabled = CONF.service_plugins
    plugins_enabled_after_disable = []
    for i in plugins_enabled:
        if i != plugin:
            plugins_enabled_after_disable.append(i)
    if len(plugins_enabled) == len(plugins_enabled_after_disable):
        LOG.error("Service plugin {} is not enabled.".format(plugin))
        restart = False
        return
    plugins_enabled_after_disable = ",".join(plugins_enabled_after_disable)
    replace(neutron_config_file_path, '^service_plugins.*',
            'service_plugins = {}'.format(plugins_enabled_after_disable))
    LOG.info("Service plugin {} is now disabled.".format(plugin))

def discover_available_plugins():
    global plugins_dict
    plugins_dict = {}
    stevedore_plugins = ExtensionManager(namespace='neutron.service_plugins', invoke_on_load=False)
    try:
        for k, v in stevedore_plugins.items():
            plugin = {}
            plugin['long_name'] = v.entry_point_target
            plugin['provides'] = v.entry_point_target.split(".")[0]
            plugins_dict[k] = plugin
    except Exception:
        pass

def my_args_parser():
    parser = argparse.ArgumentParser()
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument('-e', '--enable', action="store",
                       dest="plugin_to_enable",
                       help="Plugin to enable")
    group.add_argument('-d', '--disable', action="store",
                       dest="plugin_to_disable",
                       help="Plugin to disable")
    parser.add_argument('-n', '--no-restart', action="store_false",
                       dest="restart", help="Force not restart neutron.")
    parser.set_defaults(restart=True)
    parsed = vars(parser.parse_args())

    if parsed.get('plugin_to_disable') is not None:
        if not parsed.get('restart'):
            plugin_disable(str(parsed.get('plugin_to_disable')))
            unlink_configs(str(parsed.get('plugin_to_disable')), restart=False)
        else:
            plugin_disable(str(parsed.get('plugin_to_disable')))
            unlink_configs(str(parsed.get('plugin_to_disable')))

    if parsed.get('plugin_to_enable') is not None:
        if not parsed.get('restart'):
            plugin_enable(str(parsed.get('plugin_to_enable')))
            link_configs(str(parsed.get('plugin_to_enable')), restart=False)
        else:
            plugin_enable(str(parsed.get('plugin_to_enable')))
            link_configs(str(parsed.get('plugin_to_enable')))

def main(*args):
    discover_available_plugins()
    my_args_parser()

if __name__ == "__main__":
    main()
