# Author: Luo Gangyi <luogangyi@chinamobile.com>

import os
import eventlet
from datetime import datetime

from oslo.config import cfg
from novaclient.v1_1 import client
from novaclient import base

from models import Instance
from models import get_session
from ping_ckeck import PingCheck
from email_notifier import SmtpAlarmNotifier

REBUILD_AFTER_REBOOT = 120

CLI_OPTIONS = [
    cfg.StrOpt('os-username',
               deprecated_group="DEFAULT",
               default=os.environ.get('OS_USERNAME', 'admin'),
               help='User name to use for OpenStack service access.'),
    cfg.StrOpt('os-password',
               deprecated_group="DEFAULT",
               secret=True,
               default=os.environ.get('OS_PASSWORD', 'admin'),
               help='Password to use for OpenStack service access.'),
    cfg.StrOpt('os-tenant-id',
               deprecated_group="DEFAULT",
               default=os.environ.get('OS_TENANT_ID', ''),
               help='Tenant ID to use for OpenStack service access.'),
    cfg.StrOpt('os-tenant-name',
               deprecated_group="DEFAULT",
               default=os.environ.get('OS_TENANT_NAME', 'admin'),
               help='Tenant name to use for OpenStack service access.'),
    cfg.StrOpt('os-cacert',
               default=os.environ.get('OS_CACERT'),
               help='Certificate chain for SSL validation.'),
    cfg.StrOpt('os-auth-url',
               deprecated_group="DEFAULT",
               default=os.environ.get('OS_AUTH_URL',
                                      'http://localhost:5000/v2.0'),
               help='Auth URL to use for OpenStack service access.'),
    cfg.StrOpt('os-region-name',
               deprecated_group="DEFAULT",
               default=os.environ.get('OS_REGION_NAME'),
               help='Region name to use for OpenStack service endpoints.'),
    cfg.StrOpt('os-endpoint-type',
               default=os.environ.get('OS_ENDPOINT_TYPE', 'publicURL'),
               help='Type of endpoint in Identity service catalog to use for '
                    'communication with OpenStack services.'),
    cfg.BoolOpt('insecure',
                default=False,
                help='Disables X.509 certificate validation when an '
                     'SSL connection to Identity Service is established.'),
]

VMHA_OPTIONS = [
    cfg.StrOpt('default_gateway',
               deprecated_group="DEFAULT",
               default='',
               help='Default Gateway to check the connectivity.'),
]

cfg.CONF.register_cli_opts(VMHA_OPTIONS)
cfg.CONF.register_cli_opts(CLI_OPTIONS, group="service_credentials")
cfg.CONF(default_config_files=['/etc/VMHA/vm_ha.conf'])

class VMHA(object):
    """
    Continuesly ping virtual machine to check whether is is alive.
    """

    def __init__(self):
        conf = cfg.CONF.service_credentials
        tenant = conf.os_tenant_id or conf.os_tenant_name
        self.nova_client = client.Client(
            username=conf.os_username,
            api_key=conf.os_password,
            project_id=tenant,
            auth_url=conf.os_auth_url,
            no_cache=True)
        self.vm_map = {}
        self.default_gateway = cfg.CONF.default_gateway
        self.session = get_session()
        self.pool = eventlet.GreenPool(200)

    def get_vm_list(self):
        """
        initiate virtual machine list from database
        """

        vm_list = {}

        for instance in self.session.query(Instance).filter(
                Instance.enabled == True).all():
            server = self.nova_client.servers.get(instance.instance_id)
            if server.status.lower() != 'active':
                print('instance %s is not in ACTIVE state, ignore!' % instance.instance_id)
                continue
            vm_list[instance.instance_id] = instance

        return vm_list

    def vm_alive_check(self):
        """
        start a greenthread to  whether a virtual machine is online
        :return:
        """

        vm_list = self.get_vm_list()
        # terminate no-exist and disabled instance's ping check

        remove_list = []
        for pingCheck in self.vm_map.values():
            if pingCheck.instance_id not in vm_list:
                # stop the ping check thread
                pingCheck.stop_check()
                remove_list.append(pingCheck.instance_id)
        for instance_id in remove_list:
            self.vm_map.pop(instance_id)

        for instance in vm_list.values():
            if instance.instance_id not in self.vm_map:

                pingCheck = PingCheck(instance_id=instance.instance_id,
                                      server_ip=instance.ip,
                                      timeout=instance.timeout,
                                      offline=instance.offline,
                                      gateway=instance.gateway,
                                      threadpool=self.pool)
                pingCheck.start_check()
                # save a (Instance,PingCheck,GreenThread) tuple
                # into vm_map
                self.vm_map[instance.instance_id] = pingCheck

            else:
                # update configuration
                pingCheck = self.vm_map[instance.instance_id]
                pingCheck.timeout = instance.timeout
                pingCheck.offline = instance.offline
                pingCheck.server_ip = instance.ip
                if instance.gateway is not None:
                    pingCheck.gateway = instance.gateway
                print "update config!", instance.ip, instance.gateway

    def vm_status_check(self):
        """
        check the status of virtual machines
        :return:
        """

        for pingCheck in self.vm_map.values():
            old_ins = self.session.query(Instance).filter(
                Instance.instance_id == pingCheck.instance_id).first()

            # if the status of vm is changed and the network status is OK
            if (old_ins.current_status != pingCheck.status
                and pingCheck.connectivity):
                #save status to db
                # self.session.query(Instance).filter(
                #     Instance.instance_id == pingCheck.instance_id).update(
                #     {Instance.current_status: pingCheck.status}
                # )
                old_ins.current_status = pingCheck.status
                self.session.commit()

                # only we find ping vm is out of time and
                # ping gateway is OK, then we can confirm
                # the vm is offline
                if pingCheck.status == 'offline':
                    self.pool.spawn(self.offline_action, pingCheck.instance_id)

                elif (pingCheck.status == 'online' and
                      old_ins.current_status is not None):
                    self.pool.spawn(self.online_action, pingCheck.instance_id)

    def online_action(self, instance_id):
        print instance_id, "is online now!"

        instance = self.session.query(Instance).filter(
                    Instance.instance_id == instance_id).first()

        # if instance.allow_email:
        #     self.do_send_email(instance)


    def offline_action(self, instance_id):
        print "warning!!!", instance_id, "is offline now!"

        instance = self.session.query(Instance).filter(
                    Instance.instance_id == instance_id).first()

        if instance.allow_email:
            self.do_send_email(instance)

        if instance.allow_rebuild:
            self.do_rebulid(instance)
        elif instance.allow_reboot:
            self.do_reboot(instance)

    def do_send_email(self, instance):

        target_address = instance.email_address
        title = '%s is offline!' % instance.instance_name
        detail = 'Instance %s (%s, %s) is offline' % (
                  instance.instance_name,
                  instance.instance_id,
                  instance.ip)
        content = "An Alarm was triggered!\r\n\r\n" \
                  "Alarm Time: %s \r\n" \
                  "Alarm Detail:\r\n%s \r\n\r\n" \
                  "This email is auto-generated by BCEC, " \
                  "do not reply it!" % \
                  (datetime.now(), detail)
        SmtpAlarmNotifier.send_mail(target_address, title, content)

    def do_reboot(self, instance):
        server = self.nova_client.servers.get(instance.instance_id)

        if server is not None:
            print("Rebooting instance %s." % instance.instance_id)
            server.reboot('HARD')
        else:
            print('Reboot ERROR:Cannot find instance %s' % instance.instance_id)

    def do_rebulid(self, instance):

        print("Rebuild instance %s. Try reboot first!" % instance.instance_id)
        self.do_reboot(instance)

        eventlet.sleep(REBUILD_AFTER_REBOOT)

        pingCheck = self.vm_map.get(instance.instance_id)

        if pingCheck is None:
            print('Rebuild ERROR:Cannot find instance %s' % instance.instance_id)

        if pingCheck.connectivity and pingCheck.status=='offline':

            server = self.nova_client.servers.get(instance.instance_id)

            if server is not None:
                image_id = instance.rebuild_image
                if image_id is None:
                    print('Rebuild ERROR:No image configured for instance %s'
                          % instance.instance_id)
                    return
                image = self.nova_client.images.get(image_id)
                if image is None:
                    print('Rebuild ERROR:Wrong image configured for instance %s'
                          % instance.instance_id)
                    return

                print("Rebuilding instance %s from image %s." %
                      (instance.instance_id, instance.rebuild_image))
                server.rebuild(image)
            else:
                print('Rebuild ERROR:Cannot find instance %s' % instance.instance_id)


    def start_vm_alive_check(self):

        while True:

            self.pool.spawn(self.vm_alive_check)
            self.pool.spawn(self.vm_status_check)

            eventlet.sleep(5)


if __name__ == "__main__":
    vmha = VMHA()
    vmha.start_vm_alive_check()


