#!/usr/bin/env python

import os
import sys
import time
import inspect
import logging
import argparse
import oslo_config.cfg
import requests.exceptions

def is_forced_down(connection, hostname):
    services = connection.services.list(host=hostname, binary="nova-compute")
    for service in services:
        if service.forced_down:
            return True
    return False

def evacuations_done(connection, hostname):
    # Get a list of migrations.
    #  :param host: (optional) filter migrations by host name.
    #  :param status: (optional) filter migrations by status.
    #  :param cell_name: (optional) filter migrations for a cell.
    #
    migrations = connection.migrations.list(host=hostname)

    print("Checking %d migrations" % len(migrations))
    for migration in migrations:
        # print migration.to_dict()
        #
        # {
        # u'status': u'error',
        # u'dest_host': None,
        # u'new_instance_type_id': 2,
        # u'old_instance_type_id': 2,
        # u'updated_at': u'2018-04-22T20:55:29.000000',
        # u'dest_compute':
        #   u'overcloud-novacompute-2.localdomain',
        # u'migration_type': u'live-migration',
        # u'source_node':
        #   u'overcloud-novacompute-0.localdomain',
        # u'id': 8,
        # u'created_at': u'2018-04-22T20:52:58.000000',
        # u'instance_uuid':
        #   u'd1c82ce8-3dc5-48db-b59f-854b3b984ef1',
        # u'dest_node':
        #   u'overcloud-novacompute-2.localdomain',
        # u'source_compute':
        #   u'overcloud-novacompute-0.localdomain'
        # }
        # Acceptable: done, completed, failed
        if migration.status in ["running", "accepted", "pre-migrating"]:
            return False
    return True

def safe_to_start(connection, hostname):
    if is_forced_down(connection, hostname):
        print("Waiting for fence-down flag to be cleared")
        return False
    if not evacuations_done(connection, hostname):
        print("Waiting for evacuations to complete or fail")
        return False
    return True

def create_nova_connection(options):
    try:
        from novaclient import client
        from novaclient.exceptions import NotAcceptable
    except ImportError:
        print("Nova not found or not accessible")
        sys.exit(1)

    from keystoneauth1 import loading
    from keystoneauth1 import session

    # Prefer the oldest and strip the leading 'v'
    kwargs = dict(
        auth_url=options["auth_url"][0],
        username=options["username"][0],
        password=options["password"][0],
        project_name=options["project_name"][0],
        user_domain_name=options["user_domain_name"][0],
        project_domain_name=options["project_domain_name"][0],
        )

    loader = loading.get_plugin_loader('password')
    keystone_auth = loader.load_from_options(**kwargs)
    keystone_session = session.Session(auth=keystone_auth, verify=(not options["insecure"]))

    nova_endpoint_type = 'internalURL'
    # We default to internalURL but we allow this to be overridden via
    # the [placement]/os_interface key.
    if 'os_interface' in options and len(options["os_interface"]) == 1:
        nova_endpoint_type = options["os_interface"][0]
    # Via https://review.opendev.org/#/c/492247/ os_interface has been deprecated in queens
    # and we need to use 'valid_interfaces' which is a:
    # "List of interfaces, in order of preference, for endpoint URL. (list value)"
    # Since it is not explicitely set in nova.conf we still keep the check for os_interface
    elif 'valid_interfaces' in options and len(options["valid_interfaces"]) >= 1:
        nova_endpoint_type = options["valid_interfaces"][0]

    # This mimicks the code in novaclient/shell.py
    if nova_endpoint_type in ['internal', 'public', 'admin']:
        nova_endpoint_type += 'URL'

    if 'region_name' in options:
        region = options['region_name'][0]
    elif 'os_region_name' in options:
        region = options['os_region_name'][0]
    else: # We actually try to make a client call even with an empty region
        region = None
    nova_versions = [ "2.23", "2" ]
    for version in nova_versions:
        nova = client.Client(version,
                             region_name=region,
                             session=keystone_session, auth=keystone_auth,
                             http_log_debug="verbose" in options,
                             endpoint_type=nova_endpoint_type)

        try:
            nova.hypervisors.list()
            return nova

        except NotAcceptable as e:
            logging.warning(e)

        except Exception as e:
            logging.warning("Nova connection failed. %s: %s" % (e.__class__.__name__, e))

    print("Couldn't obtain a supported connection to nova, tried: %s\n" % repr(nova_versions))
    return None


parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('--config-file', dest='nova_config', action='store',
                    default="/etc/nova/nova.conf",
                    help='path to nova configuration (default: /etc/nova/nova.conf)')
parser.add_argument('--nova-binary', dest='nova_binary', action='store',
                    default="/usr/bin/nova-compute",
                    help='path to nova compute binary (default: /usr/bin/nova-compute)')
parser.add_argument('--enable-file', dest='enable_file', action='store',
                    default="/var/lib/nova/instanceha/enabled",
                    help='file exists if instance HA is enabled on this host '\
                    '(default: /var/lib/nova/instanceha/enabled)')


sections = {}
(args, remaining) = parser.parse_known_args(sys.argv)

config = oslo_config.cfg.ConfigParser(args.nova_config, sections)
config.parse()
config.sections["placement"]["insecure"] = 0
config.sections["placement"]["verbose"] = 1

if os.path.isfile(args.enable_file):
    connection = None
    while not connection:
        # Loop in case the control plane is recovering when we run
        connection = create_nova_connection(config.sections["placement"])
        if not connection:
            time.sleep(10)

    while not safe_to_start(connection, config.sections["DEFAULT"]["host"][0]):
        time.sleep(10)

real_args = [args.nova_binary, '--config-file', args.nova_config]
real_args.extend(remaining[1:])
os.execv(args.nova_binary, real_args)
