# -*- coding: utf-8 -*-
#
# This file is part of Karesansui.
#
# Copyright (C) 2009 HDE, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#

import web
import re

import karesansui
from karesansui.lib.rest import Rest, auth
from karesansui.lib.const import ICON_DIR_TPL, \
     VIRT_COMMAND_CREATE_GUEST, VIRT_COMMAND_REPLICATE_GUEST, \
     VIRT_COMMAND_DELETE_GUEST, \
     VNC_PORT_MIN_NUMBER, PORT_MAX_NUMBER, \
     MEMORY_MIN_SIZE, DISK_MIN_SIZE, \
     VIRT_LIBVIRT_DATA_DIR

from karesansui.lib.utils import comma_split, \
     generate_mac_address, is_param, \
     next_number, generate_uuid, string_from_uuid, uniq_sort, \
     uni_force, get_partition_info, chk_create_disk, json_dumps, \
     get_ifconfig_info

from karesansui.lib.checker import Checker, \
    CHECK_EMPTY, CHECK_VALID, CHECK_LENGTH, CHECK_ONLYSPACE, \
    CHECK_MIN, CHECK_MAX, CHECK_EXIST

from karesansui.lib.const import \
    NOTE_TITLE_MIN_LENGTH, NOTE_TITLE_MAX_LENGTH, \
    MACHINE_NAME_MIN_LENGTH, MACHINE_NAME_MAX_LENGTH, \
    TAG_MIN_LENGTH, TAG_MAX_LENGTH, \
    VNC_PORT_MIN_NUMBER, VNC_PORT_MAX_NUMBER, \
    HYPERVISOR_MIN_SIZE, HYPERVISOR_MAX_SIZE, \
    MEMORY_MIN_SIZE, DISK_MIN_SIZE, \
    DOMAIN_NAME_MIN_LENGTH, DOMAIN_NAME_MAX_LENGTH

from karesansui.lib.virt.virt import KaresansuiVirtConnection

from karesansui.lib.merge import  MergeGuest

from karesansui.db.model.machine import HYPERVISOR, ATTRIBUTE
from karesansui.db.access.machine import \
     findbyhost1guestall, findbyhost1, \
     findbyguest1, \
     new as m_new, save as m_save, delete as m_delete

from karesansui.db.access.machine2jobgroup import new as m2j_new, save as m2j_save
from karesansui.db.access.notebook import new as n_new
from karesansui.db.access.tag import new as t_new, samecount as t_count, findby1name as t_name
from karesansui.db.access.tag import new as t_new, samecount as t_count, findby1name as t_name
from karesansui.db.access._2pysilhouette import jg_save, jg_delete
from karesansui.db.model._2pysilhouette import Job, JobGroup

from pysilhouette.command import dict2command

def validates_guest_add(obj):
    checker = Checker()
    check = True

    _ = obj._
    checker.errors = []

    if not is_param(obj.input, 'm_name'):
        check = False
        checker.add_error(_('Parameter m_name does not exist.'))
    else:
        check = checker.check_string(
                    _('Machine Name'),
                    obj.input.m_name,
                    CHECK_EMPTY | CHECK_LENGTH | CHECK_ONLYSPACE,
                    None,
                    min = MACHINE_NAME_MIN_LENGTH,
                    max = MACHINE_NAME_MAX_LENGTH,
            ) and check

    if is_param(obj.input, 'note_title'):
        check = checker.check_string(
                    _('Title'),
                    obj.input.note_title,
                    CHECK_LENGTH | CHECK_ONLYSPACE,
                    None,
                    min = NOTE_TITLE_MIN_LENGTH,
                    max = NOTE_TITLE_MAX_LENGTH,
                ) and check

    if is_param(obj.input, 'note_value'):
        check = checker.check_string(
                    _('Note'),
                    obj.input.note_value,
                    CHECK_ONLYSPACE,
                    None,
                    None,
                    None,
                ) and check

    if is_param(obj.input, 'tags'):
        for tag in comma_split(obj.input.tags):
            check = checker.check_string(
                        _('Tag'),
                        tag,
                        CHECK_LENGTH | CHECK_ONLYSPACE,
                        None,
                        min = TAG_MIN_LENGTH,
                        max = TAG_MAX_LENGTH,
                    ) and check


    if not is_param(obj.input, 'm_hypervisor'):
        check = False
        checker.add_error(_('Parameter m_hypervisor does not exist.'))
    else:
        check = checker.check_hypervisor(
                _('Hypervisor'),
                obj.input.m_hypervisor,
                CHECK_EMPTY | CHECK_VALID | CHECK_MIN | CHECK_MAX,
                HYPERVISOR_MIN_SIZE,
                HYPERVISOR_MAX_SIZE,
            ) and check

    if not is_param(obj.input, 'domain_name'):
        check = False
        checker.add_error(_('Parameter domain_name does not exist.'))
    else:
        check = checker.check_string(
                _('Domain Name'),
                obj.input.domain_name,
                CHECK_EMPTY | CHECK_VALID | CHECK_LENGTH | CHECK_ONLYSPACE,
                '[^-a-zA-Z0-9_\.]+',
                DOMAIN_NAME_MIN_LENGTH,
                DOMAIN_NAME_MAX_LENGTH,
            ) and check

    if is_param(obj.input, 'xen_mem_size'):
        check = checker.check_number(
                _('Memory Size (MB)'),
                obj.input.xen_mem_size,
                CHECK_VALID | CHECK_MIN | CHECK_EMPTY,
                MEMORY_MIN_SIZE,
                None,
            ) and check

    if is_param(obj.input, 'xen_disk_size'):
        check = checker.check_number(
                _('Disk Size (MB)'),
                obj.input.xen_disk_size,
                CHECK_VALID | CHECK_MIN | CHECK_EMPTY,
                DISK_MIN_SIZE,
                None,
            ) and check

    if not is_param(obj.input, 'xen_kernel'):
        check = False
        checker.add_error(_('Parameter xen_kernel does not exist.'))
    else:
        check = checker.check_startfile(
                _('Kernel Image'),
                obj.input.xen_kernel,
                CHECK_EMPTY | CHECK_VALID | CHECK_EXIST,
            ) and check

    if not is_param(obj.input, 'xen_initrd'):
        check = False
        checker.add_error(_('Parameter xen_initrd does not exist.'))
    else:
        check = checker.check_startfile(
                _('Initrd Image'),
                obj.input.xen_initrd,
                CHECK_EMPTY | CHECK_VALID | CHECK_EXIST,
            ) and check

    if not is_param(obj.input, 'xen_vncport'):
        check = False
        checker.add_error(_('Parameter xen_vncport does not exist.'))
    else:
        check = checker.check_number(
                _('VNC Port Number'),
                obj.input.xen_vncport,
                CHECK_EMPTY | CHECK_VALID | CHECK_MIN | CHECK_MAX,
                VNC_PORT_MIN_NUMBER,
                VNC_PORT_MAX_NUMBER,
            ) and check

    if not is_param(obj.input, 'xen_mac'):
        check = False
        checker.add_error(_('Parameter xen_mac does not exist.'))
    else:
        check = checker.check_macaddr(
                _('MAC Address'),
                obj.input.xen_mac,
                CHECK_EMPTY | CHECK_VALID,
            ) and check

    obj.view.alert = checker.errors
    return check


# public method
def regist_guest(obj, _guest, icon_filename,
                  cmd, options, cmdname, rollback_options):

    if icon_filename:
        _guest.icon = icon_filename

    if (karesansui.sheconf.has_key('env.uniqkey') is False) \
           or (karesansui.sheconf['env.uniqkey'].strip('') == ''):
        raise 

    action_cmd = dict2command(
        "%s/%s" % (karesansui.config['application.bin.dir'], cmd),
        options)

    rollback_cmd = dict2command(
        "%s/%s" % (karesansui.config['application.bin.dir'], VIRT_COMMAND_DELETE_GUEST),
        rollback_options)

    _jobgroup = JobGroup(cmdname[0], karesansui.sheconf['env.uniqkey'])
    _job = Job('%s command' % cmdname[1], 0, action_cmd)
    _job.rollback_command = rollback_cmd
    _jobgroup.jobs.append(_job)
    
    # GuestOS INSERT
    try:
        m_save(obj.orm, _guest)
        obj.orm.commit()
    except:
        obj.logger.error('Failed to register the Guest OS. #1 - guest name=%s' \
                          % _guest.name)
        raise # throw
    
    # JobGroup INSERT
    try:
        jg_save(obj.pysilhouette.orm, _jobgroup)
        obj.pysilhouette.orm.commit()
    except:
        # rollback(machine)
        obj.logger.error('Failed to register the JobGroup. #2 - jobgroup name=%s' \
                          % _jobgroup.name)
            
        try:
            m_delete(obj.orm, _guest)
            obj.orm.commit()
            obj.logger.error('#3 Rollback successful. - guest id=%d' % _guest.id)
        except:
            obj.logger.critical('#4 Rollback failed. - guest id=%d' % _guest.id)
            raise
            
        raise # throw

    # Machine2JobGroup INSERT
    try:
        _m2j = m2j_new(machine=_guest,
                       jobgroup_id=_jobgroup.id,
                       uniq_key=karesansui.sheconf['env.uniqkey'],
                       created_user=obj.me,
                       modified_user=obj.me,
                       )
        m2j_save(obj.orm, _m2j)
        obj.orm.commit()
    except:
        # rollback(machine, jobgroup)
        try:
            m_delete(obj.orm, _guest)
            obj.orm.commit()
        except:
            # rollback(machine)
            obj.logger.critical('Failed to register the Machine. #5 - guest id=%d' \
                              % _guest.id)
        try:
            jg_delete(obj.pysilhouette.orm, _jobgroup)
            obj.pysilhouette.orm.commit()
        except:
            # rollback(jobgroup)
            obj.logger.critical('Failed to register the JobGroup. #6 - jobgroup id=%d' \
                              % _jobgroup.id)
        raise # throw

    return True

class Guest(Rest):

    def _post(self, f):
        ret = Rest._post(self, f)
        if hasattr(self, "kvc") is True:
            self.kvc.close()
        return ret
    
    @auth
    def _GET(self, *param, **params):
        host_id = self.chk_hostby1(param)
        if host_id is None: return web.notfound()

        self.kvc = KaresansuiVirtConnection()
        if self.is_mode_input() is True:
            self.view.host_id = host_id
            self.view.hypervisors = HYPERVISOR
            self.view.mac_address = generate_mac_address()

            phydev = []
            phydev_regex = re.compile(r"xenbr")
            for dev,dev_info in get_ifconfig_info().iteritems():
                try:
                    if phydev_regex.match(dev):
                        phydev.append(dev)
                except:
                    pass
            if len(phydev) == 0:
                phydev.append("xenbr0")
            phydev.sort()
            self.view.phydev = phydev # Physical device
            self.view.virnet = sorted(self.kvc.list_active_network()) # Virtual device

            partition_info = get_partition_info(VIRT_LIBVIRT_DATA_DIR)
            disk_info = {"host_max_disk": int(partition_info[1][0]),
                         "guest_alloc_disk":int(partition_info[2][0]),
                         "host_free_disk":int(partition_info[3][0]),
                         }

            self.view.min_disk = DISK_MIN_SIZE
            self.view.max_disk = disk_info["host_max_disk"]

            try:
                used_ports = self.kvc.list_used_vnc_port()
                self.view.vnc_port = next_number(VNC_PORT_MIN_NUMBER,
                                                 PORT_MAX_NUMBER,
                                                 used_ports)
                mem_info = self.kvc.get_mem_info()
                self.view.min_mem = MEMORY_MIN_SIZE
                self.view.max_mem = mem_info['host_max_mem']
            except:
                self.kvc.close()
                raise

            return True

        else:
            models = findbyhost1guestall(self.orm, host_id)
            if not models: # machine not found.
                return True
            try:
                guests = []
                for model in models:
                    domname = self.kvc.uuid_to_domname(model.uniq_key)
                    #if not domname: return web.conflict(web.ctx.path)
                    virt = self.kvc.search_kvg_guests(domname)[0]
                    guests.append(MergeGuest(model, virt))
                if self.is_json() is True:
                    guests_json = []
                    for x in guests:
                        guests_json.append(x.get_json(self.me.languages))
                        
                    self.view.guests = json_dumps(guests_json)
                else:
                    self.view.guests = guests
            except:
                self.kvc.close()
                raise
                    
            return True

    @auth
    def _POST(self, *param, **params):
        host_id = self.chk_hostby1(param)
        if host_id is None: return web.notfound()

        if not validates_guest_add(self):
            return web.badrequest(self.view.alert)

        self.kvc = KaresansuiVirtConnection()
        try:
            active_guests = self.kvc.list_active_guest()
            inactive_guests = self.kvc.list_inactive_guest()
            used_vnc_ports = self.kvc.list_used_vnc_port()
            used_mac_addrs = self.kvc.list_used_mac_addr()
            mem_info = self.kvc.get_mem_info()
        except:
            self.kvc.close()
            raise

        # Check on whether value has already been used
        # Guest OS
        if (self.input.domain_name in active_guests) \
               or (self.input.domain_name in inactive_guests):
            return web.conflict(web.ctx.path, "Guest OS is already there.")
        # VNC port number
        if(int(self.input.xen_vncport) in used_vnc_ports):
            return web.conflict(web.ctx.path, "VNC Port is already there.")
        # MAC addr
        if(self.input.xen_mac in used_mac_addrs):
            return web.conflict(web.ctx.path, "MAC Address is already there.")

        # disk
        if not chk_create_disk(VIRT_LIBVIRT_DATA_DIR, self.input.xen_disk_size):
            return web.badrequest('No available disk size.')

        uuid = string_from_uuid(generate_uuid())
        
        options = {}
        options['uuid'] = uuid
        
        if is_param(self.input, "domain_name"):
            options['name'] = self.input.domain_name
        if is_param(self.input, "xen_mem_size"):
            options['mem-size'] = self.input.xen_mem_size
        if is_param(self.input, "xen_disk_size"):
            options['disk-size'] = self.input.xen_disk_size
        if is_param(self.input, "xen_kernel"):
            options['kernel'] = self.input.xen_kernel
        if is_param(self.input, "xen_initrd"):
            options['initrd'] = self.input.xen_initrd
        if is_param(self.input, "xen_vncport"):
            options['vnc-port'] = self.input.xen_vncport
        if is_param(self.input, "xen_mac"):
            options['mac'] = self.input.xen_mac
        if is_param(self.input, "xen_extra"):
            options['extra'] = self.input.xen_extra
        if is_param(self.input, "nic_type"):
            if self.input.nic_type == "phydev":
                options['interface-format'] = "b:" + self.input.phydev
            elif self.input.nic_type == "virnet":
                options['interface-format'] = "n:" + self.input.virnet

        if int(self.input.m_hypervisor) == HYPERVISOR['XEN']:
            i_hypervisor = HYPERVISOR['XEN']
        elif int(self.input.m_hypervisor) == HYPERVISOR['KVM']:
            i_hypervisor = HYPERVISOR['KVM']
        else:
            return web.badrequest("This is not the hypervisor.")

        host = findbyhost1(self.orm, host_id)

        # notebook
        note_title = None
        if is_param(self.input, "note_title"):
            note_title = self.input.note_title

        note_value = None
        if is_param(self.input, "note_value"):
            note_value = self.input.note_value
            
        _notebook = n_new(note_title, note_value)

        # tags
        _tags = None
        if is_param(self.input, "tags"):
            _tags = []
            tag_array = comma_split(self.input.tags)
            tag_array = uniq_sort(tag_array)
            for x in tag_array:
                if t_count(self.orm, x) == 0:
                    _tags.append(t_new(x))
                else:
                    _tags.append(t_name(self.orm, x))

        # Icon
        icon_filename = None
        if is_param(self.input, "icon_filename", empty=True):
            icon_filename = self.input.icon_filename
            
        _guest = m_new(created_user=self.me,
                       modified_user=self.me,
                       uniq_key=uni_force(uuid),
                       name=self.input.m_name,
                       attribute=ATTRIBUTE['GUEST'],
                       hypervisor=i_hypervisor,
                       notebook=_notebook,
                       tags=_tags,
                       icon=icon_filename,
                       is_deleted=False,
                       parent=host,
                       )

        ret =  regist_guest(self,
                             _guest,
                             icon_filename,
                             VIRT_COMMAND_CREATE_GUEST,
                             options,
                             ['Create Guest', 'Create Guest'],
                             {"name": options['name']}
                             )
        if ret is True:
            return web.accepted()
        else:
            return False

urls = (
    '/host/(\d+)/guest/?(\.part|\.json)$', Guest,
    )
