"""
Virt management features

Copyright 2007, Red Hat, Inc
Michael DeHaan <mdehaan@redhat.com>

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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""

# warning: virt management is rather complicated
# to see a simple example of func, look at the
# service control module.  API docs on how
# to use this to come.

# other modules
import os
import sub_process
import libvirt

# our modules
import codes
import func_module

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

class FuncLibvirtConnection(object):

    version = "0.0.1"
    api_version = "0.0.1"
    description = "Virtualization items through func."

    def __init__(self):

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

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

        if not conn:
            raise codes.FuncException("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 codes.FuncException("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()


class Virt(func_module.FuncModule):

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

    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])
            }
        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, vm):
	self.conn = self.__get_conn()
	if self.conn.get_type() == "Xen":
	    autostart_args = [
		"/bin/ln",
		"-s",
		"/etc/xen/%s" % vm,
		"/etc/xen/auto"
	    ]
        else:
            # When using KVM, we need to make sure the autostart
            # directory exists
	    mkdir_args = [
		"/bin/mkdir",
		"-p",
		"/etc/libvirt/qemu/autostart"
	    ]
            sub_process.call(mkdir_args,shell=False,close_fds=True)

            # We aren't using virsh autostart because we want
            # the command to work even when the VM isn't running
	    autostart_args = [
		"/bin/ln",
		"-s",
		"/etc/libvirt/qemu/%s.xml" % vm,
		"/etc/libvirt/qemu/autostart/%s.xml" % vm
	    ]

        return sub_process.call(autostart_args,shell=False,close_fds=True)

    def freemem(self):
        self.conn = self.__get_conn()
        # Start with the physical memory and subtract
        memory = self.conn.nodeinfo()[1]

        # Take 256M off which is reserved for Domain-0
        memory = memory - 256

        vms = self.conn.find_vm(-1)
        for vm in vms:
            # Exclude stopped vms and Domain-0 by using
            # ids greater than 0
            if vm.ID() > 0:
                # This node is active - remove its memory (in bytes)
                memory = memory - int(vm.info()[2])/1024

        return memory

    def install(self, server_name, target_name, system=False, virt_name=None, virt_path=None, graphics=False):

        """
        Install a new virt system by way of a named cobbler profile.
        """

        # Example:
        # install("bootserver.example.org", "fc7webserver", True)
        # install("bootserver.example.org", "client.example.org", True, "client-disk0", "HostVolGroup00")

        conn = self.__get_conn()

        if conn is None:
            raise codes.FuncException("no connection")

        if not os.path.exists("/usr/bin/koan"):
            raise codes.FuncException("no /usr/bin/koan")
        target = "profile"
        if system:
            target = "system"

        koan_args = [
            "/usr/bin/koan",
            "--virt",
            "--%s=%s" % (target, target_name),
            "--server=%s" % server_name
        ]

        if virt_name:
            koan_args.append("--virt-name=%s" % virt_name)

        if virt_path:
            koan_args.append("--virt-path=%s" % virt_path)

        if not graphics:
            koan_args.append("--nogfx")

        rc = sub_process.call(koan_args,shell=False,close_fds=True)
        if rc == 0:
            return 0
        else:
            raise codes.FuncException("koan returned %d" % rc)


    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()
        self.conn.suspend(vmid)
        return 0


    def unpause(self, vmid):

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

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


    def create(self, vmid):

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


    def destroy(self, vmid):

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


    def undefine(self, vmid):

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

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


    def get_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)
