#!/usr/bin/env python

# Common functions for managing statically-attached (ie onboot, without xapi) VDIs

import sys, os, subprocess
import XenAPI, inventory, xmlrpclib

main_dir = "/etc/xensource/static-vdis"

def read_whole_file(filename):
    f = open(filename)
    try:
        return reduce(lambda x, y: x + y, f.readlines(), "").strip()
    finally:
        f.close()

def write_whole_file(filename, contents):
    f = open(filename, "w")
    try:
        f.write(contents)
    finally:
        f.close()

def load(name):
    """Return a dictionary describing a single static VDI"""
    d = { "id": name }
    for key in [ "vdi-uuid", "config", "reason" ]:
        d[key] = read_whole_file("%s/%s/%s" % (main_dir, name, key))
    try:
        disk = "%s/%s/disk" % (main_dir, name)
        os.stat(disk) # throws an error if missing
        d["disk"] = os.readlink(disk)
    except:
        pass
    dnb = "False"
    try:
        os.stat("%s/%s/delete-next-boot" % (main_dir, name))
        dnb = "True"
    except:
        pass
    d["delete-next-boot"] = dnb
    return d

def list():
    all = []
    try:
        all = os.listdir(main_dir)
    except:
        pass
    return map(load, all)

def fresh_name():
    all = []
    try:
        all = os.listdir(main_dir)
        for i in range(0, len(all) + 1): # guarantees to find a unique number
            i = str(i)
            if not(i in all):
                return i
    except:
        # Directory doesn't exist
        os.mkdir(main_dir)
        return "0"
        

def to_string_list(d):
    keys = [ "vdi-uuid", "reason", "currently-attached", "delete-next-boot" "path" ]
    m = 0
    for key in keys:
        if len(key) > m:
            m = m + len(key)
    def left(key, value):
        return key + (" " * (m - len(key))) + ": " + value
    def right(key, value):
        return (" " * (m - len(key))) + key + ": " + value
    l = [ left("vdi-uuid", d["vdi-uuid"]), right("reason", d["reason"]) ]
    l.append(right("delete-next-boot", d["delete-next-boot"]))
    if d.has_key("disk"):
        l.append(right("currently-attached", "True"))
        l.append(right("path", d['disk']))
    else:
        l.append(right("currently-attached", "False"))
    return l

def add(session, vdi_uuid, reason):
    for existing in list():
        if existing['vdi-uuid'] == vdi_uuid:
            if existing['delete-next-boot'] == "True":
                # Undo the 'delete-next-boot' flag to reinstitute
                path = main_dir + "/" + existing['id']
                os.unlink(path + "/delete-next-boot")
                os.unlink(path + "/reason")
                write_whole_file(path + "/reason", reason)
                # Assume config is still valid
                return
            raise "Static configuration for VDI already exists"
    
    vdi = session.xenapi.VDI.get_by_uuid(vdi_uuid)
    host = session.xenapi.host.get_by_uuid(inventory.get_localhost_uuid ())
    
    config = None
    try:
        config = session.xenapi.VDI.generate_config(host, vdi)
    except XenAPI.Failure, e:
        raise "Failed generating static config file: %s" % (str(e))
    sr = session.xenapi.VDI.get_SR(vdi)
    ty = session.xenapi.SR.get_type(sr)
    filename = None
    all_sm = session.xenapi.SM.get_all_records()
    for sm_ref in all_sm.keys():
        if all_sm[sm_ref]['type'] == ty:
            filename = session.xenapi.SM.get_driver_filename(sm_ref)
    if filename == None:
        raise "Unable to discover SM plugin driver filename"

    # Make a fresh directory in main_dir to store the configuration. Note
    # there is no locking so please run this script serially.
    fresh = fresh_name()
    path = main_dir + "/" + fresh
    os.mkdir(path)
    write_whole_file(path + "/vdi-uuid", vdi_uuid)
    write_whole_file(path + "/config", config)
    write_whole_file(path + "/reason", reason)
    os.symlink(filename, path + "/driver")

def delete(vdi_uuid):
    found = False
    for existing in list():
        if existing['vdi-uuid'] == vdi_uuid:
            found = True
            path = main_dir + "/" + existing['id']
            f = open(path + "/delete-next-boot", "w")
            f.close()
            # If not currently attached then zap the whole tree
            if not(existing.has_key("disk")):
                os.system("/bin/rm -rf %s" % path)
    if not found:
        raise "Disk configuration not found"

# Copied by util.py
def doexec(args, inputtext=None):
    """Execute a subprocess, then return its return code, stdout and stderr"""
    proc = subprocess.Popen(args,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,close_fds=True)
    (stdout,stderr) = proc.communicate(inputtext)
    rc = proc.returncode
    return (rc,stdout,stderr)

def call_backend_attach(driver, config):
    xml = doexec([ driver, config ])
    if xml[0] <> 0:
        raise "SM_BACKEND_FAILURE(%d, %s, %s)" % xml
    xmlrpc = xmlrpclib.loads(xml[1])
    path = xmlrpc[0][0]
    return path

def attach(vdi_uuid):
    found = False
    for existing in list():
        if existing['vdi-uuid'] == vdi_uuid:
            found = True
            if not(existing.has_key('path')):
                d = main_dir + "/" + existing['id'] 
                # Delete any old symlink
                try:
                    os.unlink(d + "/disk")
                except:
                    pass
                config = read_whole_file(d + "/config")
                path = call_backend_attach(d + "/driver", config)
                os.symlink(path, d + "/disk")
                return d + "/disk"
    if not found:
        raise "Disk configuration not found"
    
def usage():
    print "Usage:"
    print " %s list                 -- print a list of VDIs which will be attached on host boot" % sys.argv[0]
    print " %s add <uuid> <reason>  -- make the VDI <uuid> available on host boot" % sys.argv[0]
    print " %s del <uuid>           -- cease making the VDI <uuid> available on host boot" % sys.argv[0]
    print " %s attach <uuid>        -- attach the VDI immediately" % sys.argv[0]    
    sys.exit(1)
    
if  __name__ == "__main__":
    if len(sys.argv) < 2:
        usage()
        
    if sys.argv[1] == "list" and len(sys.argv) == 2:
        for i in list ():
            for line in to_string_list(i):
                print line
            print
    elif sys.argv[1] == "add" and len(sys.argv) == 4:
        session = XenAPI.xapi_local()
        session.xenapi.login_with_password("root", "")        
        try:
            add(session, sys.argv[2], sys.argv[3])
        finally:
            session.xenapi.logout()
    elif sys.argv[1] == "del" and len(sys.argv) == 3:
        delete(sys.argv[2])
    elif sys.argv[1] == "attach" and len(sys.argv) == 3:
        path = attach(sys.argv[2])
        print path
    else:
        usage()
    
