"""
Xen Migrate
Migrate XenServer to Open Source Xen
2009 Mark Pace -- Jolokia Networks
pace@jolokianetworks.com
GPL License
USE THIS SOFTWARE AT YOUR OWN RISK!
"""

import os
import subprocess
import sys

def docmd(cmd):
    """
    run a command and return the communicate PIPE
    """
    execute=subprocess.Popen([cmd],shell=True,stdout=subprocess.PIPE)
    return execute.communicate()[0]

def exportvm(vmname,lvdev,destfile):
    """
    export lvdev to dest
    """
    # we'll need to handle difference block sizes at some point
    blocksize=1024*1024
    vmuuid=getvmuuid(vmname)
    vmstatus=getvmstatus(vmuuid)
    if vmstatus=='running':
        cmd='xe vm-shutdown -u root uuid='+vmuuid
        docmd(cmd)
    vmstatus=getvmstatus(vmuuid)
    if vmstatus=='halted':
        if not os.path.exists(destfile):
            try:
                print '\nActivating Volume:'
                cmd='lvchange -v -ay '+lvdev
                lvchange=docmd(cmd)
                source=open(lvdev,'rb')
                dest=open(destfile,'wb')
                print '\nRW notification every: '+str((blocksize*1024)/(2**30))+'GB' # 2**30=GB
                sys.stdout.write('Exporting: ')
                write=0
                while True:
                    write=write+1
                    data=source.read(blocksize)
                    if write%(blocksize/1024)==0:
                        sys.stdout.write(str(write/1024)+'GBr')
                    if len(data)==0:
                        break #EOF
                    dest.write(data)
                    if write%(blocksize/1024)==0:
                        sys.stdout.write('w ')
                    sys.stdout.flush()
            finally:
                try:
                    print
                    source.close()
                    dest.close()
                    print 'Successful export'
                finally:
                    print '\nDeactivating Volume:'
                    cmd='lvchange -v -an '+lvdev
                    docmd(cmd)
                    print 'Restart VM with:'
                    print 'xe vm-startup -u root uuid='+vmuuid
        else:
            print 'ERROR: destination file '+destfile+' exists.'
    else:
        print 'ERROR: vm status:',vmstatus,'vm needs to be halted to migrate'

def importvm(lvdest,sourcefile,vgdest,lvsize):
    """
    import a raw vmfile into a logical volume
    """
    blocksize=1024*1024
    lvexists=0
    lvvgs=getlvdevlist()
    for lvvg in lvvgs:
        if lvdest==lvvg[0]:
            print 'ERROR: lv '+lvdest+' exists cannot import'
            lvexists=1
    if not lvexists:
        cmd='lvcreate -v -n '+lvdest+' -L '+lvsize+'G '+vgdest
        print '\nCreating Logical Volume:'
        docmd(cmd)
        try:
            source=open(sourcefile,'rb')
            destlv=vgdest+'/'+lvdest
            dest=open(destlv,'wb')
            print '\nRW notification every: '+str((blocksize*1024)/(2**30))+'GB' # 2**30=GB
            sys.stdout.write('Importing: ')
            write=0
            while True:
                write=write+1
                data=source.read(blocksize)
                if write%(blocksize/1024)==0:
                    sys.stdout.write(str(write/1024)+'GBr')
                if len(data)==0:
                    break # EOF
                dest.write(data)
                if write%(blocksize/1024)==0:
                    sys.stdout.write('w ')
                sys.stdout.flush()
        finally:
            try:
                print
                source.close()
                dest.close()
                print 'Successful import'
            finally:
                print
    else:
        print 'ERROR: logical volume '+lvdest+' exists'

def getlvdevlist():
    """
    get logical volume and volume group list and return it
    """
    lvvgs=[]
    sep=','
    cmd='lvs --separator \''+sep+'\''
    vgdevs=docmd(cmd).split('\n')
    del vgdevs[0]
    del vgdevs[-1]
    for vgdev in vgdevs:
        lv=vgdev.split(sep)[0][2:]
        vg=vgdev.split(sep)[1]
        size=vgdev.split(sep)[3][:-1]
        lvvgs.append([lv,vg,size])
    return lvvgs

def getlvdevxen(vmdiskuuid):
    """
    take the vmdisk uuid and return the logical volume device name
    """
    lvvgs=getlvdevlist()
    for lvvg in lvvgs:
        if vmdiskuuid in lvvg[0]:
            lvdev='/dev/'+lvvg[1]+'/'+lvvg[0]
            return lvdev,lvvg[2]
    return None,None

def getvmdiskuuid(vmuuid):
    """
    get the vmdisk uuids from the vmuuid
    return disk uuids in list
    """
    diskuuid=[]
    cmd='xe vbd-list vm-uuid='+vmuuid
    response=docmd(cmd).split('vdi-uuid ( RO): ')
    del response[0]
    for index,uuid in enumerate(response):
        curuuid=uuid.split('\n')[0]
        if curuuid!='<not in database>':
            partid=uuid.split('\n')[2].split(': ')[1]
            diskuuid.append([curuuid,partid])
    return diskuuid

def getvmstatus(vmuuid):
    cmd='xe vm-list uuid='+vmuuid
    response=docmd(cmd).split('power-state ( RO): ')[1].split('\n')[0]
    return response

def getvmuuid(vmname):
    """
    get the vmuuid from the name-label of a vm
    return uuid
    """
    try:
        cmd='xe vm-list name-label=\''+vmname+'\''
        uuid=docmd(cmd).split(':')[1].split(' ')[1][:-1]
        return uuid
    except IndexError:
        return 'vm not found'

def reftoraw(refdir,rawfile):
    """
    take the ref directory of an xva file and create a raw importable file
    """
    blocksize=1024*1024
    refdirlist=os.listdir(refdir)
    numfiles=int(refdirlist[-2])
    if os.path.isdir(refdir):
        if not os.path.exists(rawfile):
            try:
                filenum=0
                print '\nRW notification every: '+str((blocksize*1024)/(2**30))+'GB' # 2**30=GB
                dest=open(rawfile,'wr')
                sys.stdout.write('\nConverting: ')
                while filenum<=numfiles:
                    if (filenum+1)%(blocksize/1024)==0:
                        sys.stdout.write(str((filenum+1)/1024)+'GBr')
                    filename=str(filenum)
                    while len(filename)<8:
                        filename='0'+filename
                    if os.path.exists(refdir+filename):
                        source=open(refdir+filename,'rb')
                        while True:
                            data=source.read(blocksize)
                            if len(data)==0:
                                source.close()
                                break # EOF
                            dest.write(data)
                    else:
                        dest.seek(blocksize,1)
                    if (filenum+1)%(blocksize/1024)==0:
                        sys.stdout.write('w ')
                    sys.stdout.flush()
                    filenum+=1
            finally:
                print
                try:
                    dest.close()
                    source.close()
                    print 'Successful convert'
                finally:
                    print
        else:
            print 'ERROR: rawfile '+rawfile+' exists'
    else:
        print 'ERROR: refdir '+refdir+' does not exist'
                    
##
## Main Program
##

if __name__=='__main__':
    from optparse import OptionParser
    parser=OptionParser(usage='%prog [options] vm-name|lvdest|rawfile')
    parser.add_option('-d','--disk',action='store_true',dest='disk',help='display vm disk uuids',default=False)
    parser.add_option('-l','--lvdev',action='store_true',dest='lvdev',help='display vm logical volume devices',default=False)
    parser.add_option('-x','--export',action='store',type='string',dest='export',metavar='FILE',help='export to FILE')
    parser.add_option('-i','--import',action='store',type='string',dest='doimport',metavar='FILE',help='import from FILE')
    parser.add_option('-c','--convert',action='store',type='string',dest='convert',metavar='DIR',help='convert xva ref dir to importable rawfile')
    (opts,args)=parser.parse_args()
    print '\nxenmigrate\n'
    if len(args)<1:
        parser.print_help()
        sys.exit(1)
    if opts.disk or opts.lvdev or opts.export:
        vmname=args[0]
        vmuuid=getvmuuid(vmname)
        print 'vm name-label    :',vmname
        print 'vm uuid          :',vmuuid
        vmdiskuuids=getvmdiskuuid(vmuuid)
        for vmdiskuuid in vmdiskuuids:
            print 'vm disk uuid     :',vmdiskuuid[0]
            print 'vm disk partid   :',vmdiskuuid[1]
            if opts.lvdev:
                lvdev,lvsize=getlvdevxen(vmdiskuuid[0])
                if lvdev is not None:
                    print 'vm disk dev name :',lvdevname
                    print 'vm disk size     :',lvsize+'GB'
                else:
                    print 'vm disk dev name : not found in mounted storage repositories'
    if opts.export and opts.doimport:
        print 'error: export and import cannot be run at the same time'
    elif opts.export:
        vmdiskuuids=getvmdiskuuid(vmuuid)
        for vmdiskuuid in vmdiskuuids:
            lvdev,lvsize=getlvdevxen(vmdiskuuid[0])
            if lvdev is not None:
                exportname=opts.export+'_'+vmdiskuuid[1]+'_'+lvsize
                print 'exporting        :',lvdev
                print 'to               :',exportname
                if lvdev:
                    exportvm(vmname,lvdev,exportname)
    elif opts.doimport:
        lvsize=opts.doimport.split('_')[-1]
        lvpartid=opts.doimport.split('_')[-2]
        lvdest=opts.doimport.split('_')[0].split('/')[-1]
        print 'lv dest           :',lvdest
        print 'vm partid         :',lvpartid
        print 'vm size           :',lvsize+'GB'
        importvm(lvdest,opts.doimport,args[0],lvsize)
    elif opts.convert:
        reftoraw(opts.convert,args[0])
