#
# Copyright 2012 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Refer to the README and COPYING files for full details of the license
#
 
from errno import EINTR
import SimpleXMLRPCServer
from vdsm import SecureXMLRPCServer
import json
import httplib
import logging
import libvirt
import threading
import socket
import sys
 
from vdsm import utils
from vdsm.define import doneCode, errCode
from vdsm.netinfo import getDeviceByIP
import API
from vdsm.exception import VdsmException
try:
    from gluster.api import getGlusterMethods
    _glusterEnabled = True
except ImportError:
    _glusterEnabled = False
 
 
class BindingXMLRPC(object):
    def __init__(self, cif, log, ip, port, ssl, vds_resp_timeout,
                 trust_store_path, default_bridge):
        self.cif = cif
        self.log = log
        self.serverIP = ip
        self.serverPort = port
        self.enableSSL = ssl
        self.serverRespTimeout = vds_resp_timeout
        self.trustStorePath = trust_store_path
        self.defaultBridge = default_bridge
 
        self._enabled = False
        self.server = self._createXMLRPCServer()
 
    def start(self):
        """
        Register xml-rpc functions and serve clients until stopped
        """
        @utils.traceback(on=self.log.name)
        def threaded_start():
            self._registerFunctions()
            self.server.timeout = 1
            self._enabled = True
 
            while self._enabled:
                try:
                    self.server.handle_request()
                except Exception as e:
                    if e[0] != EINTR:
                        self.log.error("xml-rpc handler exception",
                                       exc_info=True)
        self._thread = threading.Thread(target=threaded_start,
                                        name='BindingXMLRPC')
        self._thread.daemon = True
        self._thread.start()
 
    def prepareForShutdown(self):
        self._enabled = False
        self.server.server_close()
        self._thread.join()
        return {'status': doneCode}
 
    def _getKeyCertFilenames(self):
        """
        Get the locations of key and certificate files.
        """
        KEYFILE = self.trustStorePath + '/keys/vdsmkey.pem'
        CERTFILE = self.trustStorePath + '/certs/vdsmcert.pem'
        CACERT = self.trustStorePath + '/certs/cacert.pem'
        return KEYFILE, CERTFILE, CACERT
 
    def _createXMLRPCServer(self):
        """
        Create xml-rpc server over http or https.
        """
        HTTP_HEADER_FLOWID = "FlowID"
 
        threadLocal = self.cif.threadLocal
 
        server_address = (self.serverIP, int(self.serverPort))
        if self.enableSSL:
            basehandler = SecureXMLRPCServer.SecureXMLRPCRequestHandler
        else:
            basehandler = SimpleXMLRPCServer.SimpleXMLRPCRequestHandler
 
        class RequestHandler(basehandler):
 
            # Timeout for the request socket
            timeout = 60
            log = logging.getLogger("BindingXMLRPC.RequestHandler")
 
            HEADER_POOL = 'Storage-Pool-Id'
            HEADER_DOMAIN = 'Storage-Domain-Id'
            HEADER_IMAGE = 'Image-Id'
            HEADER_VOLUME = 'Volume-Id'
            HEADER_CONTENT_LENGTH = 'content-length'
            HEADER_CONTENT_TYPE = 'content-type'
 
            def setup(self):
                threadLocal.client = self.client_address[0]
                threadLocal.server = self.request.getsockname()[0]
                return basehandler.setup(self)
 
            def do_PUT(self):
                try:
                    contentLength = self.headers.getheader(
                        self.HEADER_CONTENT_LENGTH)
                    if not contentLength:
                        self.send_error(httplib.LENGTH_REQUIRED,
                                        "missing content length")
                        return
 
                    try:
                        contentLength = int(contentLength)
                    except ValueError:
                        self.send_error(httplib.BAD_REQUEST,
                                        "invalid content length %r" %
                                        contentLength)
                        return
 
                    # Required headers
                    spUUID = self.headers.getheader(self.HEADER_POOL)
                    sdUUID = self.headers.getheader(self.HEADER_DOMAIN)
                    imgUUID = self.headers.getheader(self.HEADER_IMAGE)
                    if not all((spUUID, sdUUID, imgUUID)):
                        self.send_error(httplib.BAD_REQUEST,
                                        "missing or empty required header(s):"
                                        " spUUID=%s sdUUID=%s imgUUID=%s"
                                        % (spUUID, sdUUID, imgUUID))
                        return
 
                    # Optional headers
                    volUUID = self.headers.getheader(self.HEADER_VOLUME)
 
                    uploadFinishedEvent = threading.Event()
 
                    def upload_finished():
                        uploadFinishedEvent.set()
 
                    methodArgs = {'fileObj': self.rfile,
                                  'contentLength': contentLength}
                    image = API.Image(imgUUID, spUUID, sdUUID)
                    response = image.downloadFromStream(methodArgs,
                                                        upload_finished,
                                                        volUUID)
 
                    while not uploadFinishedEvent.is_set():
                        uploadFinishedEvent.wait()
 
                    json_response = json.dumps(response)
                    self.send_response(httplib.OK)
                    self.send_header(self.HEADER_CONTENT_TYPE,
                                     'application/json')
                    self.send_header(self.HEADER_CONTENT_LENGTH,
                                     len(json_response))
                    self.end_headers()
                    self.wfile.write(json_response)
 
                except socket.timeout:
                    self.send_error(httplib.REQUEST_TIMEOUT,
                                    "request timeout")
 
                except Exception:
                    self.send_error(httplib.INTERNAL_SERVER_ERROR,
                                    "error during execution", exc_info=True)
 
            def send_error(self, error, message, exc_info=False):
                try:
                    self.log.error(message, exc_info=exc_info)
                    self.send_response(error)
                    self.end_headers()
                except Exception:
                    self.log.error("failed to return response",
                                   exc_info=True)
 
            def parse_request(self):
                r = basehandler.parse_request(self)
                threadLocal.flowID = self.headers.get(HTTP_HEADER_FLOWID)
                return r
 
            def finish(self):
                basehandler.finish(self)
                threadLocal.client = None
                threadLocal.server = None
                threadLocal.flowID = None
 
            if sys.version_info[:2] == (2, 6):
                # Override BaseHTTPServer.BaseRequestHandler implementation to
                # avoid pointless and slow attempt to get the fully qualified
                # host name from the client address. This method is not used
                # any more in Python 2.7.
                def address_string(self):
                    return self.client_address[0]
 
        if self.enableSSL:
            KEYFILE, CERTFILE, CACERT = self._getKeyCertFilenames()
            server = SecureXMLRPCServer.SecureThreadedXMLRPCServer(
                server_address,
                keyfile=KEYFILE, certfile=CERTFILE, ca_certs=CACERT,
                timeout=self.serverRespTimeout,
                requestHandler=RequestHandler)
        else:
            server = utils.SimpleThreadedXMLRPCServer(
                server_address,
                requestHandler=RequestHandler, logRequests=True)
        utils.closeOnExec(server.socket.fileno())
 
        return server
 
    def _registerFunctions(self):
        def wrapIrsMethod(f):
            def wrapper(*args, **kwargs):
                fmt = ""
                logargs = []
 
                if self.cif.threadLocal.client:
                    fmt += "client [%s]"
                    logargs.append(self.cif.threadLocal.client)
 
                if getattr(self.cif.threadLocal,
                           'flowID', None) is not None:
                    fmt += " flowID [%s]"
                    logargs.append(self.cif.threadLocal.flowID)
 
                self.log.debug(fmt, *logargs)
 
                try:
                    return f(*args, **kwargs)
                except:
                    self.log.error("Unexpected exception", exc_info=True)
                    return errCode['unexpected']
 
            wrapper.__name__ = f.__name__
            wrapper.__doc__ = f.__doc__
            return wrapper
 
        globalMethods = self.getGlobalMethods()
        irsMethods = self.getIrsMethods()
        if not self.cif.irs:
            err = errCode['recovery'].copy()
            err['status'] = err['status'].copy()
            err['status']['message'] = 'Failed to initialize storage'
            self.server._dispatch = lambda method, params: err
 
        self.server.register_introspection_functions()
        for (method, name) in globalMethods:
            self.server.register_function(wrapApiMethod(method), name)
        for (method, name) in irsMethods:
            self.server.register_function(wrapIrsMethod(method), name)
        if _glusterEnabled and self.cif.gluster:
            for (method, name) in getGlusterMethods(self.cif.gluster):
                self.server.register_function(wrapApiMethod(method), name)
 
    #
    # Callable methods:
    #
    def vmDestroy(self, vmId):
        vm = API.VM(vmId)
        return vm.destroy()
 
    def vmCreate(self, vmParams):
        vm = API.VM(vmParams['vmId'])
        return vm.create(vmParams)
 
    def getVMList(self, fullStatus=False, vmList=()):
        api = API.Global()
        return api.getVMList(fullStatus, vmList)
 
    def vmPause(self, vmId):
        vm = API.VM(vmId)
        return vm.pause()
 
    def vmCont(self, vmId):
        vm = API.VM(vmId)
        return vm.cont()
 
    def vmReset(self, vmId):
        vm = API.VM(vmId)
        return vm.reset()
 
    def vmShutdown(self, vmId, delay=None, message=None, reboot=False):
        vm = API.VM(vmId)
        return vm.shutdown(delay, message, reboot)
 
    def vmSetTicket(self, vmId, password, ttl,
                    existingConnAction='disconnect', params={}):
        vm = API.VM(vmId)
        return vm.setTicket(password, ttl, existingConnAction, params)
 
    def vmChangeCD(self, vmId, driveSpec):
        vm = API.VM(vmId)
        return vm.changeCD(driveSpec)
 
    def vmChangeFloppy(self, vmId, driveSpec):
        vm = API.VM(vmId)
        return vm.changeFloppy(driveSpec)
 
    def vmSendKeys(self, vmId, keySequence):
        vm = API.VM(vmId)
        return vm.sendKeys(keySequence)
 
    def vmMigrate(self, params):
        vm = API.VM(params['vmId'])
        return vm.migrate(params)
 
    def vmGetMigrationStatus(self, vmId):
        vm = API.VM(vmId)
        return vm.getMigrationStatus()
 
    def vmMigrationCancel(self, vmId):
        vm = API.VM(vmId)
        return vm.migrateCancel()
 
    def vmHotplugDisk(self, params):
        vm = API.VM(params['vmId'])
        return vm.hotplugDisk(params)
 
    def vmHotunplugDisk(self, params):
        vm = API.VM(params['vmId'])
        return vm.hotunplugDisk(params)
 
    def vmHotplugNic(self, params):
        vm = API.VM(params['vmId'])
        return vm.hotplugNic(params)
 
    def vmHotunplugNic(self, params):
        vm = API.VM(params['vmId'])
        return vm.hotunplugNic(params)
 
    def vmUpdateDevice(self, vmId, params):
        vm = API.VM(vmId)
        return vm.vmUpdateDevice(params)
 
    def vmSetNumberOfCpus(self, vmId, numberOfCpus):
        vm = API.VM(vmId)
        return vm.setNumberOfCpus(vmId, numberOfCpus)
 
    def vmSnapshot(self, vmId, snapDrives, snapMemVolHandle=''):
        """
        Take snapshot of VM
 
        :param snapMemVolHandle:
            memory snapshots are not supported in cluster level: default value.
            vm snapshot should contain memory: a comma-separated string of IDs:
             domain,pool,image1,volume1,image2,volume2 (hibernation volumes
             representation).
            vm snapshot should not contain memory: empty string
        :type snapMemVolHandle: string
        """
        vm = API.VM(vmId)
        return vm.snapshot(snapDrives, snapMemVolHandle)
 
    def vmMerge(self, vmId, mergeDrives):
        vm = API.VM(vmId)
        return vm.merge(mergeDrives)
 
    def vmMergeStatus(self, vmId):
        vm = API.VM(vmId)
        return vm.mergeStatus()
 
    def vmSetBalloonTarget(self, vmId, target):
        vm = API.VM(vmId)
        return vm.setBalloonTarget(target)
 
    def getCapabilities(self):
        api = API.Global()
        ret = api.getCapabilities()
        ret['info']['management_ip'] = self.serverIP
        ret['info']['lastClient'] = self.cif.threadLocal.client
        ret['info']['lastClientIface'] = getDeviceByIP(
            self.cif.threadLocal.server)
        return ret
 
    def getHardwareInfo(self):
        api = API.Global()
        return api.getHardwareInfo()
 
    def getStats(self):
        api = API.Global()
        return api.getStats()
 
    def vmGetStats(self, vmId):
        vm = API.VM(vmId)
        return vm.getStats()
 
    def getAllVmStats(self):
        api = API.Global()
        return api.getAllVmStats()
 
    def vmMigrationCreate(self, params):
        vm = API.VM(params['vmId'])
        return vm.migrationCreate(params)
 
    def vmDesktopLogin(self, vmId, domain, user, password):
        vm = API.VM(vmId)
        return vm.desktopLogin(domain, user, password)
 
    def vmDesktopLogoff(self, vmId, force):
        vm = API.VM(vmId)
        return vm.desktopLogoff(force)
 
    def vmDesktopLock(self, vmId):
        vm = API.VM(vmId)
        return vm.desktopLock()
 
    def vmDesktopSendHcCommand(self, vmId, message):
        vm = API.VM(vmId)
        return vm.desktopSendHcCommand(message)
 
    def vmHibernate(self, vmId, hiberVolHandle):
        vm = API.VM(vmId)
        return vm.hibernate(hiberVolHandle)
 
    def vmMonitorCommand(self, vmId, cmd):
        vm = API.VM(vmId)
        return vm.monitorCommand(cmd)
 
    def vmDiskReplicateStart(self, vmId, srcDisk, dstDisk):
        vm = API.VM(vmId)
        return vm.diskReplicateStart(srcDisk, dstDisk)
 
    def vmDiskReplicateFinish(self, vmId, srcDisk, dstDisk):
        vm = API.VM(vmId)
        return vm.diskReplicateFinish(srcDisk, dstDisk)
 
    def diskGetAlignment(self, vmId, driveSpecs):
        api = API.VM(vmId)
        return api.getDiskAlignment(driveSpecs)
 
    def diskSizeExtend(self, vmId, driveSpecs, newSize):
        if vmId == API.VM.BLANK_UUID:
            try:
                volume = API.Volume(
                    driveSpecs['volumeID'], driveSpecs['poolID'],
                    driveSpecs['domainID'], driveSpecs['imageID'])
            except KeyError:
                return errCode['imageErr']
            return volume.updateSize(newSize)
        else:
            vm = API.VM(vmId)
            return vm.diskSizeExtend(driveSpecs, newSize)
 
    def addNetwork(self, bridge, vlan=None, bond=None, nics=None,
                   options=None):
        api = API.Global()
        return api.addNetwork(bridge, vlan, bond, nics, options)
 
    def delNetwork(self, bridge, vlan=None, bond=None, nics=None,
                   options=None):
        api = API.Global()
        return api.delNetwork(bridge, vlan, bond, nics, options)
 
    def editNetwork(self, oldBridge, newBridge, vlan=None, bond=None,
                    nics=None, options=None):
        api = API.Global()
        return api.editNetwork(oldBridge, newBridge, vlan, bond, nics, options)
 
    def setupNetworks(self, networks, bondings, options):
        api = API.Global()
        return api.setupNetworks(networks, bondings, options)
 
    def ping(self):
        api = API.Global()
        return api.ping()
 
    def setSafeNetworkConfig(self):
        api = API.Global()
        return api.setSafeNetworkConfig()
 
    def fenceNode(self, addr, port, agent, username, password, action,
                  secure=False, options=''):
        api = API.Global()
        return api.fenceNode(addr, port, agent, username, password, action,
                             secure, options)
 
    def setLogLevel(self, level):
        api = API.Global()
        return api.setLogLevel(level)
 
    def setMOMPolicy(self, policy):
        api = API.Global()
        return api.setMOMPolicy(policy)
 
    def setMOMPolicyParameters(self, key_value_store):
        api = API.Global()
        return api.setMOMPolicyParameters(key_value_store)
 
    def setHaMaintenanceMode(self, mode, enabled):
        api = API.Global()
        return api.setHaMaintenanceMode(mode, enabled)
 
    def domainActivate(self, sdUUID, spUUID, options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.activate(spUUID)
 
    def domainAttach(self, sdUUID, spUUID, options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.attach(spUUID)
 
    def domainCreate(self, storageType, sdUUID, domainName,
                     typeSpecificArg, domClass,
                     domVersion=None, options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.create(storageType, typeSpecificArg, domainName,
                             domClass, domVersion)
 
    def domainDeactivate(self, sdUUID, spUUID, msdUUID, masterVersion,
                         options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.deactivate(spUUID, msdUUID, masterVersion)
 
    def domainDetach(self, sdUUID, spUUID, msdUUID, masterVersion,
                     options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.detach(spUUID, msdUUID, masterVersion, force=False)
 
    def domainDetachForced(self, sdUUID, spUUID, options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.detach(spUUID, None, None, force=True)
 
    def domainExtend(self, sdUUID, spUUID, devlist, force=False, options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.extend(spUUID, devlist, force)
 
    def domainFormat(self, sdUUID,
                     autoDetach=False, options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.format(autoDetach)
 
    def domainGetFileStats(self, sdUUID, pattern='*',
                           caseSensitive=False, options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.getFileStats(pattern, caseSensitive)
 
    def domainGetImages(self, sdUUID, options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.getImages()
 
    def domainGetInfo(self, sdUUID, options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.getInfo()
 
    def domainGetStats(self, sdUUID, options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.getStats()
 
    def domainGetVolumes(self, sdUUID, spUUID,
                         imgUUID=API.Image.BLANK_UUID):
        domain = API.StorageDomain(sdUUID)
        return domain.getVolumes(spUUID, imgUUID)
 
    def domainSetDescription(self, sdUUID, description, options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.setDescription(description)
 
    def domainValidate(self, sdUUID, options=None):
        domain = API.StorageDomain(sdUUID)
        return domain.validate()
 
    def imageDelete(self, sdUUID, spUUID,
                    imgUUID, postZero=False, force=False):
        image = API.Image(imgUUID, spUUID, sdUUID)
        return image.delete(postZero, force)
 
    def imageDeleteVolumes(self, sdUUID, spUUID, imgUUID, volumes,
                           postZero=False, force=False):
        image = API.Image(imgUUID, spUUID, sdUUID)
        return image.deleteVolumes(volumes, postZero, force)
 
    def imageMergeSnapshots(self, sdUUID, spUUID, vmUUID, imgUUID,
                            ancestor, successor, postZero=False):
        image = API.Image(imgUUID, spUUID, sdUUID)
        return image.mergeSnapshots(ancestor, successor, postZero)
 
    def imageMove(self, spUUID, srcDomUUID, dstDomUUID, imgUUID, vmUUID,
                  op, postZero=False, force=False):
        image = API.Image(imgUUID, spUUID, srcDomUUID)
        return image.move(dstDomUUID, op, postZero, force)
 
    def imageCloneStructure(self, spUUID, sdUUID, imgUUID, dstSdUUID):
        image = API.Image(imgUUID, spUUID, sdUUID)
        return image.cloneStructure(dstSdUUID)
 
    def imageSyncData(self, spUUID, sdUUID, imgUUID, dstSdUUID, syncType):
        image = API.Image(imgUUID, spUUID, sdUUID)
        return image.syncData(dstSdUUID, syncType)
 
    def imageUpload(self, methodArgs, spUUID, sdUUID, imgUUID, volUUID=None):
        image = API.Image(imgUUID, spUUID, sdUUID)
        return image.upload(methodArgs, volUUID)
 
    def imageDownload(self, methodArgs, spUUID, sdUUID, imgUUID, volUUID=None):
        image = API.Image(imgUUID, spUUID, sdUUID)
        return image.download(methodArgs, volUUID)
 
    def poolConnect(self, spUUID, hostID, scsiKey, msdUUID, masterVersion,
                    domainsMap=None, options=None):
        pool = API.StoragePool(spUUID)
        return pool.connect(hostID, scsiKey, msdUUID, masterVersion,
                            domainsMap)
 
    def poolConnectStorageServer(self, domType, spUUID, conList, options=None):
        pool = API.StoragePool(spUUID)
        return pool.connectStorageServer(domType, conList)
 
    def poolCreate(self, poolType, spUUID, poolName, masterDom, domList,
                   masterVersion, lockPolicy=None, lockRenewalIntervalSec=None,
                   leaseTimeSec=None, ioOpTimeoutSec=None,
                   leaseRetries=None, options=None):
        pool = API.StoragePool(spUUID)
        return pool.create(poolName, masterDom, masterVersion, domList,
                           lockRenewalIntervalSec, leaseTimeSec,
                           ioOpTimeoutSec, leaseRetries)
 
    def poolDestroy(self, spUUID, hostID, scsiKey, options=None):
        pool = API.StoragePool(spUUID)
        return pool.destroy(hostID, scsiKey)
 
    def poolDisconnect(self, spUUID, hostID, scsiKey, remove=False,
                       options=None):
        pool = API.StoragePool(spUUID)
        return pool.disconnect(hostID, scsiKey, remove)
 
    def poolDisconnectStorageServer(self, domType, spUUID, conList,
                                    options=None):
        pool = API.StoragePool(spUUID)
        return pool.disconnectStorageServer(domType, conList)
 
    def poolFenceSPMStorage(self, spUUID, lastOwner, lastLver, options=None):
        pool = API.StoragePool(spUUID)
        return pool.fence()
 
    def poolGetBackedUpVmsInfo(self, spUUID, sdUUID,
                               vmList=None, options=None):
        pool = API.StoragePool(spUUID)
        return pool.getBackedUpVmsInfo(sdUUID, vmList)
 
    def poolGetBackedUpVmsList(self, spUUID, sdUUID=None, options=None):
        pool = API.StoragePool(spUUID)
        return pool.getBackedUpVmsList(sdUUID)
 
    def poolGetFloppyList(self, spUUID, options=None):
        pool = API.StoragePool(spUUID)
        return pool.getFloppyList()
 
    def poolGetDomainsContainingImage(self, spUUID, imgUUID, options=None):
        pool = API.StoragePool(spUUID)
        return pool.getDomainsContainingImage(imgUUID)
 
    def poolGetIsoList(self, spUUID, extension='iso', options=None):
        pool = API.StoragePool(spUUID)
        return pool.getIsoList(extension)
 
    def poolGetSpmStatus(self, spUUID, options=None):
        pool = API.StoragePool(spUUID)
        return pool.getSpmStatus()
 
    def poolGetInfo(self, spUUID, options=None):
        pool = API.StoragePool(spUUID)
        return pool.getInfo()
 
    def poolMoveMultipleImages(self, spUUID, srcDomUUID, dstDomUUID, imgDict,
                               vmUUID, force=False):
        pool = API.StoragePool(spUUID)
        return pool.moveMultipleImages(srcDomUUID, dstDomUUID, imgDict, force)
 
    def poolReconstructMaster(self, spUUID, poolName, masterDom, domDict,
                              masterVersion, lockPolicy=None,
                              lockRenewalIntervalSec=None, leaseTimeSec=None,
                              ioOpTimeoutSec=None, leaseRetries=None,
                              hostId=None, options=None):
        pool = API.StoragePool(spUUID)
        return pool.reconstructMaster(
            hostId, poolName, masterDom, masterVersion, domDict,
            lockRenewalIntervalSec, leaseTimeSec, ioOpTimeoutSec, leaseRetries)
 
    def poolRefresh(self, spUUID, msdUUID, masterVersion, options=None):
        pool = API.StoragePool(spUUID)
        return pool.refresh(msdUUID, masterVersion)
 
    def poolSetDescription(self, spUUID, description, options=None):
        pool = API.StoragePool(spUUID)
        return pool.setDescription(description)
 
    def poolSpmStart(self, spUUID, prevID, prevLVER, recoveryMode,
                     scsiFencing, maxHostID=None,
                     domVersion=None, options=None):
        pool = API.StoragePool(spUUID)
        return pool.spmStart(prevID, prevLVER, scsiFencing,
                             maxHostID, domVersion)
 
    def poolSpmStop(self, spUUID, options=None):
        pool = API.StoragePool(spUUID)
        return pool.spmStop()
 
    def poolUpgrade(self, spUUID, targetDomVersion):
        pool = API.StoragePool(spUUID)
        return pool.upgrade(targetDomVersion)
 
    def poolValidateStorageServerConnection(self, domType, spUUID, conList,
                                            options=None):
        pool = API.StoragePool(spUUID)
        return pool.validateStorageServerConnection(domType, conList)
 
    def poolUpdateVMs(self, spUUID, vmList, sdUUID=None, options=None):
        pool = API.StoragePool(spUUID)
        return pool.updateVMs(vmList, sdUUID)
 
    def poolRemoveVm(self, spUUID, vmUUID, sdUUID=None, options=None):
        pool = API.StoragePool(spUUID)
        return pool.removeVM(vmUUID, sdUUID)
 
    def volumeCopy(self, sdUUID, spUUID, vmUUID, srcImgUUID, srcVolUUID,
                   dstImgUUID, dstVolUUID, description='',
                   dstSdUUID=API.StorageDomain.BLANK_UUID,
                   volType=API.Volume.Roles.SHARED,
                   volFormat=API.Volume.Formats.UNKNOWN,
                   preallocate=API.Volume.Types.UNKNOWN, postZero=False,
                   force=False):
        volume = API.Volume(srcVolUUID, spUUID, sdUUID, srcImgUUID)
        return volume.copy(dstSdUUID, dstImgUUID, dstVolUUID, description,
                           volType, volFormat, preallocate, postZero, force)
 
    def volumeCreate(self, sdUUID, spUUID, imgUUID, size, volFormat,
                     preallocate, diskType, volUUID, desc,
                     srcImgUUID=API.Image.BLANK_UUID,
                     srcVolUUID=API.Volume.BLANK_UUID):
        volume = API.Volume(volUUID, spUUID, sdUUID, imgUUID)
        return volume.create(size, volFormat, preallocate, diskType, desc,
                             srcImgUUID, srcVolUUID)
 
    def volumeExtendSize(self, spUUID, sdUUID, imgUUID, volUUID, newSize):
        volume = API.Volume(volUUID, spUUID, sdUUID, imgUUID)
        return volume.extendSize(newSize)
 
    def volumeGetInfo(self, sdUUID, spUUID, imgUUID, volUUID):
        volume = API.Volume(volUUID, spUUID, sdUUID, imgUUID)
        return volume.getInfo()
 
    def volumeGetPath(self, sdUUID, spUUID, imgUUID, volUUID):
        volume = API.Volume(volUUID, spUUID, sdUUID, imgUUID)
        return volume.getPath()
 
    def volumeGetSize(self, sdUUID, spUUID, imgUUID, volUUID):
        volume = API.Volume(volUUID, spUUID, sdUUID, imgUUID)
        return volume.getSize()
 
    def volumeSetSize(self, sdUUID, spUUID, imgUUID, volUUID, newSize):
        volume = API.Volume(volUUID, spUUID, sdUUID, imgUUID)
        return volume.setSize(newSize)
 
    def volumePrepare(self, sdUUID, spUUID, imgUUID, volUUID, rw=True):
        volume = API.Volume(volUUID, spUUID, sdUUID, imgUUID)
        return volume.prepare(rw)
 
    def volumeRefresh(self, sdUUID, spUUID, imgUUID, volUUID):
        volume = API.Volume(volUUID, spUUID, sdUUID, imgUUID)
        return volume.refresh()
 
    def volumeSetDescription(self, sdUUID, spUUID, imgUUID, volUUID,
                             description):
        volume = API.Volume(volUUID, spUUID, sdUUID, imgUUID)
        return volume.setDescription(description)
 
    def volumeSetLegality(self, sdUUID, spUUID, imgUUID, volUUID, legality):
        volume = API.Volume(volUUID, spUUID, sdUUID, imgUUID)
        return volume.setLegality(legality)
 
    def volumeTearDown(self, sdUUID, spUUID, imgUUID, volUUID):
        volume = API.Volume(volUUID, spUUID, sdUUID, imgUUID)
        return volume.tearDown()
 
    def taskClear(self, taskId):
        task = API.Task(taskId)
        return task.clear()
 
    def taskGetInfo(self, taskId):
        task = API.Task(taskId)
        return task.getInfo()
 
    def taskGetStatus(self, taskId):
        task = API.Task(taskId)
        return task.getStatus()
 
    def taskRevert(self, taskId):
        task = API.Task(taskId)
        return task.revert()
 
    def taskStop(self, taskId):
        task = API.Task(taskId)
        return task.stop()
 
    # Global storage methods
    def tasksGetAllInfo(self):
        api = API.Global()
        return api.getAllTasksInfo()
 
    def tasksGetAllStatuses(self):
        api = API.Global()
        return api.getAllTasksStatuses()
 
    def tasksGetAll(self, options=None):
        api = API.Global()
        return api.getAllTasks()
 
    def iscsiDiscoverSendTargets(self, con, options=None):
        iscsiConn = API.ISCSIConnection(con['connection'], con['port'],
                                        con['user'], con['password'])
        return iscsiConn.discoverSendTargets()
 
    def vgCreate(self, name, devlist, force=False):
        vg = API.LVMVolumeGroup(self.cif)
        return vg.create(name, devlist, force)
 
    def vgGetInfo(self, vgUUID, options=None):
        vg = API.LVMVolumeGroup(vgUUID)
        return vg.getInfo()
 
    def vgRemove(self, vgUUID, options=None):
        vg = API.LVMVolumeGroup(vgUUID)
        return vg.remove()
 
    def domainsGetList(self, spUUID=None, domainClass=None, storageType=None,
                       remotePath=None, options=None):
        api = API.Global()
        return api.getStorageDomains(spUUID, domainClass, storageType,
                                     remotePath)
 
    def poolsGetConnectedList(self, options=None):
        api = API.Global()
        return api.getConnectedStoragePools()
 
    def storageRepoGetStats(self, options=None):
        api = API.Global()
        return api.getStorageRepoStats()
 
    def startMonitoringDomain(self, sdUUID, hostID, options=None):
        api = API.Global()
        return api.startMonitoringDomain(sdUUID, hostID)
 
    def stopMonitoringDomain(self, sdUUID, options=None):
        api = API.Global()
        return api.stopMonitoringDomain(sdUUID)
 
    def vgsGetList(self, storageType=None, options=None):
        api = API.Global()
        return api.getLVMVolumeGroups(storageType)
 
    def devicesGetList(self, storageType=None, options=None):
        api = API.Global()
        return api.getDeviceList(storageType)
 
    def devicesGetVisibility(self, guids, options=None):
        api = API.Global()
        return api.getDevicesVisibility(guids)
 
    def storageServerConnectionRefsAcquire(self, conRefArgs):
        return API.ConnectionRefs().acquire(conRefArgs)
 
    def storageServerConnectionRefsRelease(self, refIDs):
        return API.ConnectionRefs().release(refIDs)
 
    def storageServerConnectionRefsStatuses(self):
        return API.ConnectionRefs().statuses()
 
    def getGlobalMethods(self):
        return ((self.vmDestroy, 'destroy'),
                (self.vmCreate, 'create'),
                (self.getVMList, 'list'),
                (self.vmPause, 'pause'),
                (self.vmCont, 'cont'),
                (self.vmSnapshot, 'snapshot'),
                (self.vmMerge, 'merge'),
                (self.vmMergeStatus, 'mergeStatus'),
                (self.vmSetBalloonTarget, 'setBalloonTarget'),
                (self.vmReset, 'reset'),
                (self.vmShutdown, 'shutdown'),
                (self.vmSetTicket, 'setVmTicket'),
                (self.vmChangeCD, 'changeCD'),
                (self.vmChangeFloppy, 'changeFloppy'),
                (self.vmSendKeys, 'sendkeys'),
                (self.vmMigrate, 'migrate'),
                (self.vmGetMigrationStatus, 'migrateStatus'),
                (self.vmMigrationCancel, 'migrateCancel'),
                (self.getCapabilities, 'getVdsCapabilities'),
                (self.getHardwareInfo, 'getVdsHardwareInfo'),
                (self.diskGetAlignment, 'getDiskAlignment'),
                (self.getStats, 'getVdsStats'),
                (self.vmGetStats, 'getVmStats'),
                (self.getAllVmStats, 'getAllVmStats'),
                (self.vmMigrationCreate, 'migrationCreate'),
                (self.vmDesktopLogin, 'desktopLogin'),
                (self.vmDesktopLogoff, 'desktopLogoff'),
                (self.vmDesktopLock, 'desktopLock'),
                (self.vmDesktopSendHcCommand, 'sendHcCmdToDesktop'),
                (self.vmHibernate, 'hibernate'),
                (self.vmMonitorCommand, 'monitorCommand'),
                (self.vmDiskReplicateStart, 'diskReplicateStart'),
                (self.vmDiskReplicateFinish, 'diskReplicateFinish'),
                (self.diskSizeExtend, 'diskSizeExtend'),
                (self.addNetwork, 'addNetwork'),
                (self.delNetwork, 'delNetwork'),
                (self.editNetwork, 'editNetwork'),
                (self.setupNetworks, 'setupNetworks'),
                (self.ping, 'ping'),
                (self.setSafeNetworkConfig, 'setSafeNetworkConfig'),
                (self.fenceNode, 'fenceNode'),
                (self.prepareForShutdown, 'prepareForShutdown'),
                (self.setLogLevel, 'setLogLevel'),
                (self.setMOMPolicy, 'setMOMPolicy'),
                (self.setMOMPolicyParameters, 'setMOMPolicyParameters'),
                (self.setHaMaintenanceMode, 'setHaMaintenanceMode'),
                (self.vmHotplugDisk, 'hotplugDisk'),
                (self.vmHotunplugDisk, 'hotunplugDisk'),
                (self.vmHotplugNic, 'hotplugNic'),
                (self.vmHotunplugNic, 'hotunplugNic'),
                (self.vmUpdateDevice, 'vmUpdateDevice'),
                (self.vmSetNumberOfCpus, 'setNumberOfCpus'))
 
    def getIrsMethods(self):
        return ((self.domainActivate, 'activateStorageDomain'),
                (self.domainAttach, 'attachStorageDomain'),
                (self.domainCreate, 'createStorageDomain'),
                (self.domainDeactivate, 'deactivateStorageDomain'),
                (self.domainDetach, 'detachStorageDomain'),
                (self.domainDetachForced, 'forcedDetachStorageDomain'),
                (self.domainExtend, 'extendStorageDomain'),
                (self.domainFormat, 'formatStorageDomain'),
                (self.domainGetFileStats, 'getFileStats'),
                (self.domainGetImages, 'getImagesList'),
                (self.domainGetInfo, 'getStorageDomainInfo'),
                (self.domainGetStats, 'getStorageDomainStats'),
                (self.domainGetVolumes, 'getVolumesList'),
                (self.domainSetDescription, 'setStorageDomainDescription'),
                (self.domainValidate, 'validateStorageDomain'),
                (self.imageDelete, 'deleteImage'),
                (self.imageDeleteVolumes, 'deleteVolume'),
                (self.imageMergeSnapshots, 'mergeSnapshots'),
                (self.imageMove, 'moveImage'),
                (self.imageCloneStructure, 'cloneImageStructure'),
                (self.imageSyncData, 'syncImageData'),
                (self.imageUpload, 'uploadImage'),
                (self.imageDownload, 'downloadImage'),
                (self.poolConnect, 'connectStoragePool'),
                (self.poolConnectStorageServer, 'connectStorageServer'),
                (self.poolCreate, 'createStoragePool'),
                (self.poolDestroy, 'destroyStoragePool'),
                (self.poolDisconnect, 'disconnectStoragePool'),
                (self.poolDisconnectStorageServer, 'disconnectStorageServer'),
                (self.poolFenceSPMStorage, 'fenceSpmStorage'),
                (self.poolGetBackedUpVmsInfo, 'getVmsInfo'),
                (self.poolGetBackedUpVmsList, 'getVmsList'),
                (self.poolGetFloppyList, 'getFloppyList'),
                (self.poolGetDomainsContainingImage, 'getImageDomainsList'),
                (self.poolGetIsoList, 'getIsoList'),
                (self.poolGetSpmStatus, 'getSpmStatus'),
                (self.poolGetInfo, 'getStoragePoolInfo'),
                (self.poolMoveMultipleImages, 'moveMultipleImages'),
                (self.poolReconstructMaster, 'reconstructMaster'),
                (self.poolRefresh, 'refreshStoragePool'),
                (self.poolSetDescription, 'setStoragePoolDescription'),
                (self.poolSpmStart, 'spmStart'),
                (self.poolSpmStop, 'spmStop'),
                (self.poolUpgrade, 'upgradeStoragePool'),
                (self.poolValidateStorageServerConnection,
                 'validateStorageServerConnection'),
                (self.poolUpdateVMs, 'updateVM'),
                (self.poolRemoveVm, 'removeVM'),
                (self.taskClear, 'clearTask'),
                (self.taskGetInfo, 'getTaskInfo'),
                (self.taskGetStatus, 'getTaskStatus'),
                (self.taskRevert, 'revertTask'),
                (self.taskStop, 'stopTask'),
                (self.volumeCopy, 'copyImage'),
                (self.volumeCreate, 'createVolume'),
                (self.volumeExtendSize, 'extendVolumeSize'),
                (self.volumeGetInfo, 'getVolumeInfo'),
                (self.volumeGetPath, 'getVolumePath'),
                (self.volumeGetSize, 'getVolumeSize'),
                (self.volumeSetSize, 'volumeSetSize'),
                (self.volumePrepare, 'prepareVolume'),
                (self.volumeRefresh, 'refreshVolume'),
                (self.volumeSetDescription, 'setVolumeDescription'),
                (self.volumeSetLegality, 'setVolumeLegality'),
                (self.volumeTearDown, 'teardownVolume'),
                (self.tasksGetAllInfo, 'getAllTasksInfo'),
                (self.tasksGetAllStatuses, 'getAllTasksStatuses'),
                (self.tasksGetAll, 'getAllTasks'),
                (self.iscsiDiscoverSendTargets, 'discoverSendTargets'),
                (self.vgCreate, 'createVG'),
                (self.vgGetInfo, 'getVGInfo'),
                (self.vgRemove, 'removeVG'),
                (self.domainsGetList, 'getStorageDomainsList'),
                (self.poolsGetConnectedList, 'getConnectedStoragePoolsList'),
                (self.storageRepoGetStats, 'repoStats'),
                (self.startMonitoringDomain, 'startMonitoringDomain'),
                (self.stopMonitoringDomain, 'stopMonitoringDomain'),
                (self.vgsGetList, 'getVGList'),
                (self.devicesGetList, 'getDeviceList'),
                (self.devicesGetVisibility, 'getDevicesVisibility'),
                (self.storageServerConnectionRefsAcquire,
                 'storageServer_ConnectionRefs_acquire'),
                (self.storageServerConnectionRefsRelease,
                 'storageServer_ConnectionRefs_release'),
                (self.storageServerConnectionRefsStatuses,
                 'storageServer_ConnectionRefs_statuses'),)
 
 
def wrapApiMethod(f):
    def wrapper(*args, **kwargs):
        try:
            logLevel = logging.DEBUG
            if f.__name__ in ('getVMList', 'getAllVmStats', 'getStats',
                              'fenceNode'):
                logLevel = logging.TRACE
            displayArgs = args
            if f.__name__ == 'vmDesktopLogin':
                assert 'password' not in kwargs
                if len(args) > 3:
                    displayArgs = args[:3] + ('****',) + args[4:]
 
            # Logging current call
            logStr = 'client [%s]::call %s with %s %s' % \
                (getattr(f.im_self.cif.threadLocal, 'client', ''),
                 f.__name__, displayArgs, kwargs)
 
            # if flowID exists
            if getattr(f.im_self.cif.threadLocal, 'flowID', None) is not None:
                logStr += " flowID [%s]" % f.im_self.cif.threadLocal.flowID
 
            # Ready to show the log into vdsm.log
            f.im_self.log.log(logLevel, logStr)
 
            if f.im_self.cif.ready:
                res = f(*args, **kwargs)
            else:
                res = errCode['recovery']
            f.im_self.cif.log.log(logLevel, 'return %s with %s',
                                  f.__name__, res)
            return res
        except libvirt.libvirtError as e:
            f.im_self.cif.log.error("libvirt error", exc_info=True)
            if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
                return errCode['noVM']
            else:
                return errCode['unexpected']
        except VdsmException as e:
            f.im_self.cif.log.error("vdsm exception occured", exc_info=True)
            return e.response()
        except:
            f.im_self.cif.log.error("unexpected error", exc_info=True)
            return errCode['unexpected']
    wrapper.__name__ = f.__name__
    wrapper.__doc__ = f.__doc__
    return wrapper