#!/usr/bin/env python

# Copyright (c) 2012 OpenStack Foundation
# All Rights Reserved.
#
#    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.

"""Neutron root wrapper for dom0.

Executes networking commands in dom0.  The XenAPI plugin is
responsible determining whether a command is safe to execute.

"""
from __future__ import print_function

from six.moves import configparser as ConfigParser
from oslo_serialization import jsonutils as json

import os
import select
import sys

import XenAPI


RC_UNAUTHORIZED = 99
RC_NOCOMMAND = 98
RC_BADCONFIG = 97
RC_XENAPI_ERROR = 96


def parse_args():
    # Split arguments, require at least a command
    exec_name = sys.argv.pop(0)
    # argv[0] required; path to conf file
    if len(sys.argv) < 2:
        sys.stderr.write("%s: No command specified" % exec_name)
        sys.exit(RC_NOCOMMAND)

    config_file = sys.argv.pop(0)
    user_args = sys.argv[:]

    return exec_name, config_file, user_args


def _xenapi_section_name(config):
    sections = [sect for sect in config.sections() if sect.lower() == "xenapi"]
    if len(sections) == 1:
        return sections[0]

    sys.stderr.write("Multiple [xenapi] sections or no [xenapi] section found!")
    sys.exit(RC_BADCONFIG)


def load_configuration(exec_name, config_file):
    config = ConfigParser.RawConfigParser()
    config.read(config_file)
    try:
        exec_dirs = config.get("DEFAULT", "exec_dirs").split(",")
        filters_path = config.get("DEFAULT", "filters_path").split(",")
        section = _xenapi_section_name(config)
        url = config.get(section, "xenapi_connection_url")
        username = config.get(section, "xenapi_connection_username")
        password = config.get(section, "xenapi_connection_password")
    except ConfigParser.Error:
        sys.stderr.write("%s: Incorrect configuration file: %s" %
                         (exec_name, config_file))
        sys.exit(RC_BADCONFIG)
    if not url or not password:
        msg = ("%s: Must specify xenapi_connection_url, "
               "xenapi_connection_username (optionally), and "
               "xenapi_connection_password in %s") % (exec_name, config_file)
        sys.stderr.write(msg)
        sys.exit(RC_BADCONFIG)
    return dict(
        filters_path=filters_path,
        url=url,
        username=username,
        password=password,
        exec_dirs=exec_dirs,
    )


def filter_command(exec_name, filters_path, user_args, exec_dirs):
    # Add ../ to sys.path to allow running from branch
    possible_topdir = os.path.normpath(os.path.join(os.path.abspath(exec_name),
                                                    os.pardir, os.pardir))
    if os.path.exists(os.path.join(possible_topdir, "neutron", "__init__.py")):
        sys.path.insert(0, possible_topdir)

    from oslo_rootwrap import wrapper

    # Execute command if it matches any of the loaded filters
    filters = wrapper.load_filters(filters_path)
    filter_match = wrapper.match_filter(
        filters, user_args, exec_dirs=exec_dirs)
    if not filter_match:
        sys.stderr.write("Unauthorized command: %s" % ' '.join(user_args))
        sys.exit(RC_UNAUTHORIZED)


def run_command(url, username, password, user_args, cmd_input):
    try:
        session = XenAPI.Session(url)
        session.login_with_password(username, password)
        try:
            host = session.xenapi.session.get_this_host(session.handle)
            result = session.xenapi.host.call_plugin(
                host, 'netwrap', 'run_command',
                {'cmd': json.dumps(user_args), 'cmd_input': json.dumps(cmd_input)})
            result_dict = json.loads(result)
            returncode = result_dict.get('returncode')
            captured_stdout = result_dict.get('out')
            captured_stderr = result_dict.get('err')
            sys.stdout.write(captured_stdout)
            sys.stderr.write(captured_stderr)
            sys.exit(returncode)
        finally:
            session.xenapi.session.logout()
    except Exception as e:
        sys.stderr.write("Failed to execute command in Dom0, %s" % e)
        sys.exit(RC_XENAPI_ERROR)


def main():
    exec_name, config_file, user_args = parse_args()
    config = load_configuration(exec_name, config_file)
    filter_command(exec_name, config['filters_path'], user_args, config['exec_dirs'])

    # If data is available on the standard input, we need to pass it to the
    # command executed in dom0
    cmd_input = None
    if select.select([sys.stdin,],[],[],0.0)[0]:
        cmd_input = "".join(sys.stdin)

    return run_command(config['url'], config['username'], config['password'],
                       user_args, cmd_input)


if __name__ == '__main__':
    main()
