#!/usr/bin/python 
"""
Virt management features

Copyright 2007, 2012 Red Hat, Inc
Michael DeHaan <mdehaan@redhat.com>
Seth Vidal <skvidal@fedoraproject.org>

This software may be freely redistributed under the terms of the GNU
general public license.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

VIRT_FAILED = 1
VIRT_SUCCESS = 0
VIRT_UNAVAILABLE=2

try:
    import json
except ImportError:
    import simplejson as json


# other modules
import os
import sys
import subprocess
try:
    import libvirt
except ImportError:
    print json.dumps({
        "failed" : True,
        "msg" : "libvirt python module unavailable",
        "rc": VIRT_UNAVAILABLE,
        })   
    sys.exit(1)


import shlex

VIRT_STATE_NAME_MAP = {
   0 : "running",
   1 : "running",
   2 : "running",
   3 : "paused",
   4 : "shutdown",
   5 : "shutdown",
   6 : "crashed"
}

class LibvirtConnection(object):

    def __init__(self):

        cmd = subprocess.Popen("uname -r", shell=True, stdout=subprocess.PIPE,
                                close_fds=True)
        output = cmd.communicate()[0]

        if output.find("xen") != -1:
            conn = libvirt.open(None)
        else:
            conn = libvirt.open("qemu:///system")

        if not conn:
            raise Exception("hypervisor connection failure")

        self.conn = conn

    def find_vm(self, vmid):
        """
        Extra bonus feature: vmid = -1 returns a list of everything
        """
        conn = self.conn

        vms = []

        # this block of code borrowed from virt-manager:
        # get working domain's name
        ids = conn.listDomainsID()
        for id in ids:
            vm = conn.lookupByID(id)
            vms.append(vm)
        # get defined domain
        names = conn.listDefinedDomains()
        for name in names:
            vm = conn.lookupByName(name)
            vms.append(vm)

        if vmid == -1:
            return vms

        for vm in vms:
            if vm.name() == vmid:
                return vm

        raise Exception("virtual machine %s not found" % vmid)

    def shutdown(self, vmid):
        return self.find_vm(vmid).shutdown()

    def pause(self, vmid):
        return self.suspend(self.conn,vmid)

    def unpause(self, vmid):
        return self.resume(self.conn,vmid)

    def suspend(self, vmid):
        return self.find_vm(vmid).suspend()

    def resume(self, vmid):
        return self.find_vm(vmid).resume()

    def create(self, vmid):
        return self.find_vm(vmid).create()

    def destroy(self, vmid):
        return self.find_vm(vmid).destroy()

    def undefine(self, vmid):
        return self.find_vm(vmid).undefine()

    def get_status2(self, vm):
        state = vm.info()[0]
        # print "DEBUG: state: %s" % state
        return VIRT_STATE_NAME_MAP.get(state,"unknown")

    def get_status(self, vmid):
        state = self.find_vm(vmid).info()[0]
        return VIRT_STATE_NAME_MAP.get(state,"unknown")

    def nodeinfo(self):
        return self.conn.getInfo()

    def get_type(self):
        return self.conn.getType()

    def get_maxVcpus(self, vmid):
        vm = self.conn.lookupByName(vmid)
        return vm.maxVcpus()

    def get_maxMemory(self, vmid):
        vm = self.conn.lookupByName(vmid)
        return vm.maxMemory()

    def getFreeMemory(self):
        return self.conn.getFreeMemory()

    def get_autostart(self, vmid):
        vm = self.conn.lookupByName(vmid)
        return vm.autostart()
    
    def set_autostart(self, vmid, val):
        vm = self.conn.lookupByName(vmid)
        return vm.setAutostart(val)
    


class Virt(object):

    def __get_conn(self):
        self.conn = LibvirtConnection()
        return self.conn

    def get_vm(self, vmid):
        self.__get_conn()
        return self.conn.find_vm(vmid)
        
    def state(self):
        vms = self.list_vms()
        state = []
        for vm in vms:
            state_blurb = self.conn.get_status(vm)
            state.append("%s %s" % (vm,state_blurb))
        return state


    def info(self):
        vms = self.list_vms()
        info = dict()
        for vm in vms:
            data = self.conn.find_vm(vm).info()
            # libvirt returns maxMem, memory, and cpuTime as long()'s, which
            # xmlrpclib tries to convert to regular int's during serialization.
            # This throws exceptions, so convert them to strings here and
            # assume the other end of the xmlrpc connection can figure things
            # out or doesn't care.
            info[vm] = {
                "state"     : VIRT_STATE_NAME_MAP.get(data[0],"unknown"),
                "maxMem"    : str(data[1]),
                "memory"    : str(data[2]),
                "nrVirtCpu" : data[3],
                "cpuTime"   : str(data[4]),
            }
            info[vm]["autostart"] =  self.conn.get_autostart(vm)

        return info

    def nodeinfo(self):
        self.__get_conn()
        info = dict()
        data = self.conn.nodeinfo()
        info = {
            "cpumodel"     : str(data[0]),
            "phymemory"    : str(data[1]),
            "cpus"         : str(data[2]),
            "cpumhz"       : str(data[3]),
            "numanodes"    : str(data[4]),
            "sockets"      : str(data[5]),
            "cpucores"     : str(data[6]),
            "cputhreads"   : str(data[7])
        }
        return info

    def list_vms(self):
        self.conn = self.__get_conn()
        vms = self.conn.find_vm(-1)
        results = []
        for x in vms:
            try:
                results.append(x.name())
            except:
                pass
        return results

    def virttype(self):
        return self.__get_conn().get_type()
        

    def autostart(self, vmid):
        self.conn = self.__get_conn()
        return self.conn.set_autostart(vmid, True)

    def freemem(self):
        self.conn = self.__get_conn()
        return self.conn.getFreeMemory()

    def shutdown(self, vmid):
        """
        Make the machine with the given vmid stop running.
        Whatever that takes.
        """
        self.__get_conn()
        self.conn.shutdown(vmid)
        return 0


    def pause(self, vmid):

        """
        Pause the machine with the given vmid.
        """
        self.__get_conn()
        return self.conn.suspend(vmid)


    def unpause(self, vmid):

        """
        Unpause the machine with the given vmid.
        """

        self.__get_conn()
        return self.conn.resume(vmid)


    def create(self, vmid):

        """
        Start the machine via the given mac address.
        """
        self.__get_conn()
        return self.conn.create(vmid)

    def start(self, vmid):

        """
        Start the machine via the given id/name
        """
        self.__get_conn()
        return self.conn.create(vmid)


    def destroy(self, vmid):

        """
        Pull the virtual power from the virtual domain, giving it virtually no
        time to virtually shut down.
        """
        self.__get_conn()
        return self.conn.destroy(vmid)


    def undefine(self, vmid):

        """
        Stop a domain, and then wipe it from the face of the earth.
        by deleting the disk image and its configuration file.
        """

        self.__get_conn()
        return self.conn.undefine(vmid)


    def status(self, vmid):

        """
        Return a state suitable for server consumption.  Aka, codes.py values, not XM output.
        """

        self.__get_conn()
        return self.conn.get_status(vmid)


    def get_xml(self, vmid):
        """
        Receive a Vm id as input
        Return an xml describing vm config returned by a libvirt call
        """
        conn = libvirt.openReadOnly(None)
        if conn == None:
            return (-1,'Failed to open connection to the hypervisor')
        try:
            domV = conn.lookupByName(vmid)
        except:
            return (-1,'Failed to find the main domain')
        return domV.XMLDesc(0)


    def get_maxVcpus(self, vmid):
        """
        Gets the max number of VCPUs on a guest
        """

        self.__get_conn()
        return self.conn.get_maxVcpus(vmid)

    def get_max_memory(self, vmid):
        """
        Gets the max memory on a guest
        """

        self.__get_conn()
        return self.conn.get_MaxMemory(vmid)

def main():
    rc = VIRT_SUCCESS
    vm_commands = ['create','status', 'start', 'stop', 'pause', 'unpause', 
                'shutdown', 'undefine', 'destroy', 'get_xml', 'autostart']
    host_commands = ['freemem', 'list_vms', 'info', 'nodeinfo', 'virttype']
    
    msg = """
    virtmodule arguments:
      state=[running|shutdown] guest=guestname
      command=some_virt_command  [guest=guestname]
    """
    
    if len(sys.argv) == 1:
        return VIRT_FAILED, msg

    argfile = sys.argv[1]
    if not os.path.exists(argfile):
        msg = "Argument file not found"
        return VIRT_FAILED, msg

    args = open(argfile, 'r').read()
    items = shlex.split(args)

    if not len(items):
        return VIRT_FAILED, msg


    # guest=name state=[running|shutdown|destroyed|undefined]
    # command=[some command] [guest=name]
    
    params = {}
    if '=' not in args:
        msg = "No proper arguments provided to virt module: %s" % args
        return VIRT_FAILED, msg
        
    for x in items:
        (k, v) = x.split("=")
        params[k] = v

    state      = params.get('state', None)
    guest      = params.get('guest', None)
    command    = params.get('command', None)
    options    = params.get('options', [])

    v = Virt()
    res = {}
    
    if state:
        if not guest:
            msg = "state change requires a guest specified"
            return VIRT_FAILED, msg
      
        res['changed'] = False
        if state == 'running':
            if v.status(guest) is not 'running':
                res['changed'] = True 
                res['msg'] = v.start(guest)
        elif state == 'shutdown':
            if v.status(guest) is not 'shutdown':
                res['changed'] = True
                res['msg'] = v.shutdown(guest)
        
        return VIRT_SUCCESS, res
        
    if command:
        if command in vm_commands:
            if not guest:
                msg = "%s requires 1 argument: guest" % command
                return VIRT_FAILED, msg
                             
            res = getattr(v, command)(guest)
            if type(res) != dict:
                res = { command: res }
            return rc, res
        
        elif hasattr(v, command):
            res = getattr(v, command)()
            if type(res) != dict:
                res = { command: res }
            return rc, res
            
        else:
            msg = "Command %s not recognized" % basecmd
            rc = VIRT_FAILED
        
    return rc, msg
    
if __name__ == "__main__":
    try:
        rc, result = main()
    except Exception, e:
        rc = 1
        result = str(e)

    if rc != 0: # something went wrong emit the msg
        print json.dumps({
            "failed" : rc,
            "msg" : result,
            "rc": rc,
        })   
        sys.exit(rc)
    else:
        print json.dumps(result)