#!/usr/pythoncontroller/python
import random
from subprocess import *
import md5 
import os

LVM_CMD_PVDISPLAY = 'pvdisplay'
LVM_CMD_PVS = 'pvs'
LVM_CMD_VGDISPLAY = 'vgdisplay'
LVM_CMD_LVCREATE = 'lvcreate'
LVM_CMD_LVREMOVE = 'lvremove'
LVM_CMD_LVDISPLAY = 'lvdisplay'
LVM_CMD_VGSCAN = 'vgscan'
LVM_CMD_LVCHANGE = 'lvchange'
LVM_CMD_LVREDUCE = 'lvreduce'
LVM_CMD_LVEXTEND = 'lvextend'
LVM_CMD_VGCREATE = 'vgcreate'
LVM_CMD_VGREMOVE = 'vgremove'

required = {
    'getStoragehandlerInfo':[],
    'getDatastoreInfo':[],
    'getIdInfo':[],
    'getNodeInfo':[],
    'getVDiskInfo':[],
    'createDatastore':['name', 'path'],
    'deleteDatastore':['uuid'],
    'createVDisk':['name', 'size'],
    'deleteVDisk':['uuid', 'Datastore'],
    'addVDisk':['uuid','newmember_count'],
    'onlineVDisk':['uuid', 'Datastore'],
    'offlineVDisk':['uuid', 'Datastore'],
    'resizeVDisk':['uuid','Datastore', 'new_size'],
    'snapshotVDisk':['name', 'uuid', 'Datastore', 'size']
}
def logprint(st):
    print st

def getSHName():
    return "lvm"

def APIError(retVal):
    return {'result': 'FAILURE', 'error': retVal}

def genLVUuid(vg, lv):
    return '%s/%s' % (vg,lv)

def getLVPath(vg, lv):
    return '/dev/%s/%s' % (vg,lv)

def testParams(params, cmd):
    required_keys = required[cmd]
    for key in required_keys:
        if not params.has_key(key):
            return False
    return True

def wait_for_path_change(path, test_condition, timeout):
    count = 0
    timeout_millis = timeout * 1000
    while count < timeout_millis:
        if os.path.exists(path) == test_condition:
            return True
        time.sleep(0.2)
        count += 200
    return False

def scanVG():
    process = Popen([LVM_CMD_VGSCAN],shell=False,stdout=PIPE,stderr=PIPE)
    out, err = process.communicate()
    if process.returncode:
        raise Exception("Error running vgscan: %s" % err)

#--- Physical volume ---
#PV Name               /dev/sdb
#VG Name               vg1
#PV Size               1.82 TB / not usable 1.09 MB
#Allocatable           yes 
#PE Size (KByte)       4096
#Total PE              476932
#Free PE               476164
#Allocated PE          768
#PV UUID               uOvMmD-SzeC-JPPO-Hvnb-fCZA-Sdnm-SoAsCY
#And the brief output:
#/dev/sdb:vg1:3907029168:-1:8:8:-1:4096:476932:476164:768:uOvMmD-SzeC-JPPO-Hvnb-fCZA-Sdnm-SoAsCY
#device:datastore_uuid:size:-1:8:8:-1:pe_size_kb:total_pe:free_pe:allocated_pe:UUID
def getPVInfo():
    try:
        scanVG()
        process = Popen([LVM_CMD_PVS,"--noheadings","-o","pv_name,pv_uuid,pv_size,pv_free,pv_used,vg_name","--units=k"],shell=False,stdout=PIPE,stderr=PIPE)
        out, err = process.communicate()
        if process.returncode:
            return APIError("Error running pvdisplay: %s" % err)
        pvInfo = {}
        out = out[:-1]
        for line in out.split('\n'):
            attr = line.split()
            singlePVInfo = {}
            uuid = int(md5.new(attr[2]).hexdigest()[:8], 16)
            singlePVInfo['datastoremember'] = ''
            if len(attr)==7:
                singlePVInfo['datastoremember'] = attr[6]
            singlePVInfo['device'] = attr[1]
            singlePVInfo['pv_size'] = attr[3]
            singlePVInfo['pv_free'] = attr[4]
            singlePVInfo['pv_used'] = attr[5]
            pvInfo[uuid] = singlePVInfo
        return pvInfo 
    except Exception, e:
        logprint("Exception: %s" % str(e))
        return {}

#  --- Volume group ---
#  VG Name               vg1
#  System ID             
#  Format                lvm2
#  Metadata Areas        1
#  Metadata Sequence No  3
#  VG Access             read/write
#  VG Status             resizable
#  MAX LV                0
#  Cur LV                1
#  Open LV               0
#  Max PV                0
#  Cur PV                1
#  Act PV                1
#  VG Size               1.82 TB
#  PE Size               4.00 MB
#  Total PE              476932
#  Alloc PE / Size       768 / 3.00 GB
#  Free  PE / Size       476164 / 1.82 TB
#  VG UUID               oeRPtq-3zeb-nKQN-0jKc-7wYU-cqTZ-x91FBx
#  vg1:r/w:772:-1:0:1:0:-1:0:1:1:1953513472:4096:476932:768:476164:oeRPtq-3zeb-nKQN-0jKc-7wYU-cqTZ-x91FBx
#  vg1:r/w:772:-1:0:1:0:-1:0:1:1:size:pe_size_kb:total_pe:allocated_pe:free_pe:UUID
def getVGInfo():
    try:
        scanVG()
        process = Popen([LVM_CMD_VGDISPLAY,'-c'],shell=False,stdout=PIPE,stderr=PIPE)
        out, err = process.communicate()
        if process.returncode:
            return APIError("Error running vgdisplay: %s" % err)
        vgInfo = {}
        for line in out.split('\n'):
            vginfo = {}
            if line.find(':') != -1 and line.find('error') == -1:
                line = line.strip()
                attr = line.split(':')
                uuid = attr[0].strip()
                vginfo['size'] = attr[11]
                vginfo['pe_size_kb'] = attr[12]
                vginfo['total_pe'] = attr[13]
                vginfo['free_pe'] = attr[15]
                vgInfo[uuid] = vginfo

        return vgInfo
    except Exception, e:
        logprint("Exception: %s" % str(e))
        return {}

#  --- Logical volume ---
#  LV Name                /dev/vg1/testRESTDisk1
#  VG Name                vg1
#  LV UUID                2M5ZMr-iepn-WDZx-lwrh-K1tD-wRoX-IFPkgu
#  LV Write Access        read/write
#  LV Status              NOT available
#  LV Size                1.00 TB
#  Current LE             262144
#  Segments               1
#  Allocation             inherit
#  Read ahead sectors     auto
#  /dev/vg1/testRESTDisk1:vg1:3:0:-1:0:2147483648:262144:-1:0:-1:-1:-1
#  disk_path:datastore:3:0:-1:0:size_in_kb:262144:-1:0:-1:-1:-1
def getLVInfo(vg):
    try:
        scanVG()
        process = Popen([LVM_CMD_LVDISPLAY,'-c', vg],shell=False,stdout=PIPE,stderr=PIPE)
        out, err = process.communicate()
        if process.returncode:
            return APIError("Error running vgdisplay: %s" % err)
        lvInfo = {}
        for line in out.split('\n'):
            lvinfo = {}
            if line.find(':') != -1 and line.find('error') == -1:
                attr = line.split(':')
                uuid = attr[0].strip()
                lvinfo['disk_path'] = uuid
                uuid = os.path.basename(lvinfo['disk_path'])
                lvinfo['Datastore'] = attr[1]
                lvinfo['size_in_kb'] = str(int(attr[6])/2)
                lvInfo[uuid] = lvinfo

        return lvInfo
    except Exception, e:
        logprint("Exception: %s" % str(e))
        return {}

def getStoragehandlerInfo(params):
    return {'lvm':{'name':'Onapp LVM Storage Handler', 'Datastore_params':{'mandatory':'name,path'}, 'VDisk_params':{'mandatory':'name,size', 'optional':'datastore,disk_path'}}}

def getDatastoreInfo(params):
    try:
	if params.has_key('uuid'):
	    uuid = str(params['uuid'])
	    return getVGInfo()[uuid]
	else:
	    return getVGInfo()
    except Exception, e:
        return APIError(str(e))

def getIdInfo(params):
    return APIError("Method not implemented.")

def getNodeInfo(params):
    uuid = ''
    
    try:
	# list pv information using pvdisplay parse out and return the results
	if params.has_key('uuid'):
	    uuid = params['uuid']
	    return getPVInfo()[uuid]
	else:
	    return getPVInfo()
    except Exception, e:
        return APIError(str(e))

def getVDiskInfo(params):
    try:
	if params.has_key('uuid'):
	    uuid = str(params['uuid'])
	    return getLVInfo(params['Datastore'])[uuid]
	else:
	    return getLVInfo(params['Datastore'])
    except Exception, e:
        return APIError(str(e))
        
def createDatastore(params):
    retVal = 'SUCCESS'
    
    try:
	cmd = [LVM_CMD_VGCREATE, params['name'], params['path']]

	try:
	    process = Popen(cmd,shell=False,stdout=PIPE,stderr=PIPE)
	    out, err = process.communicate()
	    if process.returncode:
		raise Exception("Error running vgcreate: %s" % err)
	except Exception, e:
	    logprint("Failed to create datastore with exception, %s" % str(e))
	    return {'result':'FAILURE','Error':str(e)}
	return {'result':retVal}
    except Exception, e:
        return APIError(str(e))

def deleteDatastore(params):
    retVal = 'SUCCESS'
    
    try:
	cmd = [LVM_CMD_VGREMOVE, '-f', params['uuid']]

	try:
	    process = Popen(cmd,shell=False,stdout=PIPE,stderr=PIPE)
	    out, err = process.communicate()
	    if process.returncode:
		raise Exception("Error running vgremove: %s" % err)
	except Exception, e:
	    logprint("Failed to delete datastore with exception, %s" % str(e))
	    return {'result':'FAILURE','Error':str(e)}
	return {'result':retVal}
    except Exception, e:
        return APIError(str(e))

def createVDisk(params):
    retVal = 'SUCCESS'
    
    try:
	if params.has_key('Datastore'):
	    cmd = [LVM_CMD_LVCREATE, params['Datastore'], '-L%sG' % params['size'], '-n', params['name']]
	    
	try:
	    process = Popen(cmd,shell=False,stdout=PIPE,stderr=PIPE)
	    out, err = process.communicate()
	    if process.returncode:
		raise Exception("Error running lvcreate: %s" % err)
	except Exception, e:
	    logprint("Failed to create vdisk with exception, %s" % str(e))
	    return {'result':'FAILURE','Error':str(e)}
	return {'result':retVal}
    except Exception, e:
        return APIError(str(e))

def deleteVDisk(params):
    retVal = 'SUCCESS'
    
    try:
	cmd = [LVM_CMD_LVREMOVE, '-f', getLVPath(params['Datastore'], params['uuid'])]

	try:
	    process = Popen(cmd,shell=False,stdout=PIPE,stderr=PIPE)
	    out, err = process.communicate()
	    if process.returncode:
		raise Exception("Error running lvremove: %s" % err)
	except Exception, e:
	    logprint("Failed to delete vdisk with exception, %s" % str(e))
	    return {'result':'FAILURE','Error':str(e)}
	return {'result':retVal} 
    except Exception, e:
        return APIError(str(e))

# lvchange -ay <disk_path>
def onlineVDisk(params):
    retVal = 'SUCCESS'
    
    try:
        lvpath = getLVPath(params['Datastore'], params['uuid'])
	cmd = [LVM_CMD_LVCHANGE, '-ay', lvpath]

	try:
	    process = Popen(cmd,shell=False,stdout=PIPE,stderr=PIPE)
	    out, err = process.communicate()
	    if process.returncode:
		raise Exception("Error running lvchange: %s" % err)
	except Exception, e:
	    logprint("Failed to online vdisk with exception, %s" % str(e))
	    return {'result':'FAILURE','Error':str(e)}

        if not wait_for_path_change(lvpath, True, 20):
            raise Exception("Error waiting for path to appear: %s" % lvpath)
	return {'result':retVal}
    except Exception, e:
        return APIError(str(e))

# lvchange -an <disk_path>
def offlineVDisk(params):
    retVal = 'SUCCESS'
    
    try:
	cmd = [LVM_CMD_LVCHANGE, '-an', getLVPath(params['Datastore'], params['uuid'])]

	try:
	    process = Popen(cmd,shell=False,stdout=PIPE,stderr=PIPE)
	    out, err = process.communicate()
	    if process.returncode:
		raise Exception("Error running lvchange: %s" % err)
	except Exception, e:
	    logprint("Failed to offline vdisk with exception, %s" % str(e))
	    return {'result':'FAILURE','Error':str(e)}
	return {'result':retVal}
    except Exception, e:
        return APIError(str(e))

#lvreduce -v -f -L {new_size}G {disk_path} - resizeVDisk
#lvextend -v -f -L {new_size}G {disk_path} - resizeVDisk
def resizeVDisk(params):
    retVal = 'SUCCESS'
    try:
        params['uuid'] = str(params['uuid'])
        params['Datastore'] = str(params['Datastore'])
	current_size = getLVInfo(params['Datastore'])[params['uuid']]['size_in_kb']
     
	if int(params['new_size']) < int(current_size)/(1024*1024):
	    cmd = [LVM_CMD_LVREDUCE, '-v', '-f', '-L%sG' % params['new_size'], getLVPath(params['Datastore'], params['uuid'])]
	else:
	    cmd = [LVM_CMD_LVEXTEND, '-v', '-f', '-L%sG' % params['new_size'], getLVPath(params['Datastore'], params['uuid'])]

        try:
	    process = Popen(cmd,shell=False,stdout=PIPE,stderr=PIPE)
	    out, err = process.communicate()
            if process.returncode:
                raise Exception("Error running cmd %s, %s" % (cmd,err))
	except Exception, e:
	    logprint("Failed to resize vdisk with exception, %s" % str(e))
	    return {'result':'FAILURE','Error':str(e)}
	return {'result':retVal}
    except Exception, e:
        return APIError(str(e))

#lvcreate -s -L #{size}G -n backup-#{identifier} #{disk_path}
def snapshotVDisk(params):
    retVal = 'SUCCESS'

    try: 
	cmd = [LVM_CMD_LVCREATE, '-s', '-L%sG' % params['size'], '-n',  params['name'],  getLVPath(params['Datastore'], params['uuid'])]

	try:
	    process = Popen(cmd,shell=False,stdout=PIPE,stderr=PIPE)
	    out, err = process.communicate()
	    if process.returncode:
		raise Exception("Error running cmd %s, %s" % (cmd,err))
	except Exception, e:
	    logprint("Failed to create snapshot of vdisk with exception, %s" % str(e))
	    return {'result':'FAILURE','Error':str(e)}
	return {'result':retVal}
    except Exception, e:
        return APIError(str(e))

