#!/usr/bin/python

#
# Copyright (c) 2000-2018 Virtuozzo International GmbH. 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.

import logging
import os
import socket
import sys
import time
import traceback

try:
    from monotonic import monotonic as get_time
except ImportError:
    from time import time as get_time

from keystoneauth1.identity import v3
from keystoneauth1.session import Session
from novaclient.client import Client as NovaClient
from oslo_config import cfg

MIN_RETRY_TIME = 20

# NOTE: We use credentials specified in nova.conf for placement api.
# That's a bit odd, but it works.
placement_group = cfg.OptGroup('placement')
placement_opts = [
    cfg.StrOpt('auth_url', required=True),
    cfg.StrOpt('username', required=True),
    cfg.StrOpt('project_name', required=True),
    cfg.StrOpt('user_domain_id', required=True),
    cfg.StrOpt('project_domain_id', required=True),
    cfg.StrOpt('password', required=True),
]
CONF = cfg.CONF
CONF.register_group(placement_group)
CONF.register_opts(placement_opts, placement_group)
LOG_PATH = '/var/log/shaman/evacuate.log'
CONF_DIR = '/etc/kolla/nova-compute/'


def evacuate(host_id, event_type, event_id):
    """Search the target host by ID and schedule evaction task on it.

    :param host_ip: the IP of the failed fost.
    :param reason: why evacuation is requested.
    """

    host_ip = socket.gethostbyname(
        'management.%s.nodes.svc.vstoragedomain.' % host_id)

    reason = '%s at node %s (%s), shaman event: %s' % (
        event_type, host_ip, host_id, event_id)

    auth = v3.Password(
        auth_url=CONF.placement.auth_url,
        username=CONF.placement.username,
        password=CONF.placement.password,
        project_name=CONF.placement.project_name,
        user_domain_id=CONF.placement.user_domain_id,
        project_domain_id=CONF.placement.project_domain_id)
    session = Session(auth, verify=False)

    # NOTE (alkurbatov): 2.53 is minimum version that supports
    # HCI host evacuate method.
    nova = NovaClient('2.53', session=session)

    hypervisors = nova.hypervisors.list(detailed=True, servers=False)
    failed_hv = next((i for i in hypervisors if i.host_ip == host_ip), None)
    if not failed_hv:
        logging.error('Hypervisor with IP %s is not found', host_ip)
        return 8

    nova.hci_hosts.evacuate(failed_hv.id, reason=reason)

    logging.info('Host %s evacuation scheduled', host_ip)
    return 0


def main():
    """See '$ man shaman2', the 'Notification scripts' section
    for additional info regarding env variables meaning.
    """
    logging.basicConfig(
        filename=LOG_PATH,
        format='%(asctime)s | %(levelname)s | %(message)s',
        level=logging.INFO)

    if not os.path.exists(CONF_DIR):
        return 0

    event_type = os.getenv('EVENT')
    if event_type != 'NODE_CRASH':
        logging.info('Event %s ignored', event_type)
        return 0

    host_id = os.getenv('SRC_NODE_ID')
    if not host_id:
        logging.error('Host ID is not specified')
        return 8

    event_id = os.getenv('EVENT_ID')

    CONF(default_config_dirs=[CONF_DIR], default_config_files=[])

    # Retry in a separate process so that shaman is not blocked
    if os.fork():
        return 0

    while True:
        t1 = get_time()
        try:
            return evacuate(host_id, event_type, event_id)
        except:
            logging.error('Failed to schedule evacuation:\n%s',
                    traceback.format_exc())

        t2 = get_time()
        time.sleep(max(0, MIN_RETRY_TIME - (t2 - t1)))

if __name__ == '__main__':
    sys.exit(main())
