Ejemplo n.º 1
0
class TMCloneCache(object):
    """Clone or retrieve from cache disk image"""

    # Debug option
    PRINT_TRACE_ON_ERROR = True
    DEFAULT_VERBOSE_LEVEL = 0

    _ARGS_LEN = 3

    # Position of the provided args
    _ARG_SRC_POS = 1
    _ARG_DST_POS = 2

    _PDISK_PORT = 8445

    _CHECKSUM = 'sha1'

    _ACCEPTED_EXTRA_DISK_TYPE = [
        'DATA_IMAGE_RAW_READONLY', 'DATA_IMAGE_RAW_READ_WRITE'
    ]
    _ACCEPTED_ROOT_DISK_TYPE = 'MACHINE_IMAGE_LIVE'

    _IDENTIFIER_KEY = 'identifier'
    _COUNT_KEY = 'count'
    _TYPE_KEY = 'type'
    _OWNER_KEY = 'owner'
    _VISIBILITY_KEY = 'visibility'

    _PDISK_SUPERUSER = '******'
    _DISK_UNAUTHORIZED_VISIBILITIES = ['PRIVATE']

    def __init__(self, args, **kwargs):

        self.diskSrc = None
        self.diskDstPath = None
        self.diskDstHost = None
        self.marketplaceEndpoint = None
        self.marketplaceImageId = None
        self.pdiskImageId = None
        self.pdiskSnapshotId = None
        self.downloadedLocalImageLocation = None
        self.downloadedLocalImageSize = 0
        self.vmOwner = None

        self._parseArgs(args)

        self._initFromConfig(kwargs.get('conf_filename', ''))

        self._initPdiskClient()
        self._initMarketplaceRelated()

        self.defaultSignalHandler = None

    def _initFromConfig(self, conf_filename=''):
        config = ConfigHolder.configFileToDictWithFormattedKeys(
            conf_filename or defaultConfigFile)
        options = PDiskEndpoint.options()
        self.configHolder = ConfigHolder(options, config)
        self.configHolder.set('pdiskEndpoint', self._createPdiskEndpoint())
        self.configHolder.set('verboseLevel', self.DEFAULT_VERBOSE_LEVEL)
        self.configHolder.assign(self)

    def _initPdiskClient(self):
        self.pdiskEndpoint = self._createPdiskEndpoint()
        self.pdiskLVMDevice = self.configHolder.persistentDiskLvmDevice
        self.configHolder.set('pdiskEndpoint', self.pdiskEndpoint)
        self.pdisk = VolumeManagerFactory.create(self.configHolder)

    def _initMarketplaceRelated(self):
        self._retrieveMarketplaceInfos()
        self._initManifestDownloader()

    def _initManifestDownloader(self):
        self.manifestDownloader = ManifestDownloader(self.configHolder)

    def run(self):
        self._retrieveDisk()

    def _checkArgs(self, args):
        if len(args) != self._ARGS_LEN:
            raise ValueError('Invalid number of arguments')

    def _parseArgs(self, args):
        self._checkArgs(args)

        dst = args[self._ARG_DST_POS]
        self.diskDstHost = self._getDiskHost(dst)
        self.diskDstPath = self._getDiskPath(dst)
        self.diskSrc = args[self._ARG_SRC_POS]

    # FIXME: duplicates should be pulled into common location
    def _createPdiskEndpoint(self):
        host = self.configHolder.persistentDiskIp
        port = self.configHolder.persistentDiskPort or _PDISK_PORT
        path = self.configHolder.persistentDiskPath or ''
        path = path.strip('/')
        return 'https://%s:%s/%s' % (host, port, path)

    def _updatePDiskSrcUrlFromPublicToLocalIp(self):
        """When PDisk is running behind a proxy, KVMs usually can't connect to
        it on the public IP. Instead substitute the public IP with the local one.
        Substitution is only made if the pdisk URL points to the public IP of the
        PDisk (i.e., the source disk is located on this site).
        persistent_disk_public_base_url should be set in the configuration."""
        src_pdisk_hostname = Util.getHostnameFromUri(self.diskSrc)
        public_pdisk_hostname = Util.getHostnameFromUri(
            self.persistentDiskPublicBaseUrl) or self.persistentDiskIp
        if src_pdisk_hostname == public_pdisk_hostname:
            disk_src_parts = urlparse(self.diskSrc)
            (scheme, _, path, params, query, fragment) = disk_src_parts
            netloc = self.persistentDiskIp
            if disk_src_parts.port:
                netloc = netloc + ":" + str(disk_src_parts.port)
            self.diskSrc = urlunparse((scheme, netloc, path, params, query,
                                       fragment))

    def _retrieveDisk(self):
        if self.diskSrc.startswith('pdisk:'):
            self.diskSrc = self.diskSrc[len('pdisk:'):]  # strip prefix
            self._updatePDiskSrcUrlFromPublicToLocalIp()
            self._startFromPersisted()
        else:
            self._startFromCowSnapshot()

    def _startFromPersisted(self):
        diskId = self.diskSrc.rstrip('/').split('/').pop()
        diskType = self.pdisk.getValue('type', diskId)

        is_root_disk = self._getDiskIndex(self.diskDstPath) is 0
        if is_root_disk:
            self._checkBootDisk(diskId, diskType)
        elif diskType not in self._ACCEPTED_EXTRA_DISK_TYPE:
            raise ValueError(
                'Only %s type disks can be attached as extra disks' %
                ', '.join(self._ACCEPTED_EXTRA_DISK_TYPE))

        self._createDestinationDir()
        self._attachPDisk(self.diskSrc)
        self._incrementVolumeUserCount(diskId)

    def _incrementVolumeUserCount(self, diskId):
        user_count = self.pdisk.getVolumeUserCount(diskId)
        self.pdisk.updateVolume({self._COUNT_KEY: str(user_count + 1)}, diskId)

    def _checkBootDisk(self, diskId, diskType):
        is_live_machine_disk = diskType in self._ACCEPTED_ROOT_DISK_TYPE
        user_count = self.pdisk.getVolumeUserCount(diskId)

        if not is_live_machine_disk:
            raise Exception('Only a live persistent disk can be booted from.')
        if user_count != 0:
            raise Exception(
                'User count must be zero on the live disk to boot from.')

    def _startFromCowSnapshot(self):
        if self._cacheMiss():
            #self._retrieveAndCachePDiskImage()
            self._remotelyCachePDiskImage()

        try:
            self._checkAuthorization()
            self._createPDiskSnapshot()
            self._setSnapshotOwner()
            self._createDestinationDir()
            self._attachPDisk(self._getPDiskSnapshotURL())
        except:
            self._deletePDiskSnapshot()
            raise

    # -------------------------------------------
    # Cache management and related
    # -------------------------------------------

    def _cacheMiss(self):
        foundIds = self._getPDiskImageIdsFromMarketplaceImageId()
        if len(foundIds) > 0:
            self.pdiskImageId = foundIds[0]
            return False
        return True

    def _createDestinationDir(self):
        dstDir = dirname(self.diskDstPath)
        self._sshDst(['mkdir', '-p', dstDir],
                     'Unable to create directory %s' % dstDir)

    def _downloadImage(self):
        imageLocations = self.manifestDownloader.getImageLocations()
        self._assertLength(imageLocations, 1, atLeast=True)
        imageMarketplaceLocation = imageLocations[0]
        imageName = self._getImageIdFromURI(imageMarketplaceLocation)
        pdiskTmpStore = self._getPDiskTempStore()
        self.downloadedLocalImageLocation = '%s/%s.%s' % (
            pdiskTmpStore, int(time()), imageName)
        self._sshPDisk([
            'curl', '-H', 'accept:application/x-gzip', '-L', '-o',
            self.downloadedLocalImageLocation, imageMarketplaceLocation
        ], 'Unable to download "%s"' % imageMarketplaceLocation)

    def _checkDownloadedImageChecksum(self):
        hash_fun = self._CHECKSUM
        size_b, checksum = self._getDownloadedImageChecksum(hash_fun)
        self._validateImageSize(size_b)
        self._validateImageChecksum(checksum, hash_fun)

    def _getDownloadedImageChecksum(self, hash_fun):
        size_b, sums = Compressor.checksum_file(
            self.downloadedLocalImageLocation, [hash_fun])
        return size_b, sums[self._CHECKSUM]

    def _validateImageSize(self, size_b):
        image_size_b = self._getImageSize()
        # convert both to strings to avoid inequality because of type mismatch
        if str(size_b) != str(image_size_b):
            raise ValueError(
                "Downloaded image size (%s) doesn't match size in image manifest (%s)"
                % (size_b, image_size_b))

    def _validateImageChecksum(self, checksum, hash_fun):
        image_checksum = self._getImageChecksum(hash_fun)
        if checksum != image_checksum:
            raise ValueError('Invalid image checksum: got %s, defined %s' %
                             (checksum, image_checksum))

    def _getImageFormat(self):
        return self.manifestDownloader.getImageElementValue('format')

    def _getImageKind(self):
        return self.manifestDownloader.getImageElementValue('kind')

    def _getImageSize(self):
        return self.manifestDownloader.getImageElementValue('bytes')

    def _getImageChecksum(self, checksum):
        return self.manifestDownloader.getImageElementValue(checksum)

    def _deleteDownloadedImage(self):
        self._sshPDisk(['rm', '-f', self.downloadedLocalImageLocation],
                       'Unable to remove temporary image', True)

    # -------------------------------------------
    # Marketplace and related
    # -------------------------------------------

    def _retrieveMarketplaceInfos(self):
        # Marketplace URLs can start with either http OR https!
        if self.diskSrc.startswith(('http://', 'https://')):
            self.marketplaceEndpoint = self._getMarketplaceEndpointFromURI(
                self.diskSrc)
            self.marketplaceImageId = self._getImageIdFromURI(self.diskSrc)
        elif self.diskSrc.startswith(
                'pdisk:'):  # Ignore Marketplace if pdisk is used
            self.marketplaceEndpoint = None
            self.marketplaceImageId = None
        else:  # Local marketplace
            self.marketplaceEndpoint = 'http://localhost'
            try:
                self.marketplaceEndpoint = self.configHolder.marketplaceEndpointLocal
            except:
                pass
                # SunStone adds '<hostname>:' to the image ID
            self.marketplaceImageId = self.diskSrc.rstrip('/').split('/').pop()

        if self.marketplaceEndpoint:
            self.configHolder.set('marketplaceEndpoint',
                                  self.marketplaceEndpoint)

    def _getMarketplaceEndpointFromURI(self, uri):
        matcher = re.match("^(.*)/metadata/.*$", uri)
        return matcher.group(1)

    def _getImageIdFromURI(self, uri):
        fragments = uri.rstrip('/').split('/')
        return fragments.pop()

    def _validateMarketplaceImagePolicy(self):
        try:
            policy = Policy(self.configHolder)
            policy.check(self.marketplaceImageId)
        except:
            raise Exception('Policy validation failed')

    def _buildFullyQualifiedMarketplaceImage(self, policyCheckResult,
                                             imagePos):
        selectedImage = policyCheckResult[imagePos]
        uri = '%s/metadata/%s/%s/%s' % (
            self.marketplaceEndpoint, selectedImage.identifier,
            selectedImage.endorser, selectedImage.created)
        return uri

    def _getPDiskImageIdsFromMarketplaceImageId(self):
        return self.pdisk.search(self._IDENTIFIER_KEY, self.marketplaceImageId)

    # -------------------------------------------
    # Persistent disk and related
    # -------------------------------------------

    def _attachPDisk(self, diskSrc):
        uuid = diskSrc.rstrip('/').split('/').pop()
        turl = self.pdisk.getTurl(uuid)
        disk_name = basename(self.diskDstPath)
        vm_id = self._retrieveInstanceId()
        vm_dir = dirname(dirname(dirname(self.diskDstPath)))

        self._sshDst([
            '/usr/sbin/stratus-pdisk-client.py', '--pdisk-id', diskSrc,
            '--vm-dir', vm_dir, '--vm-id',
            str(vm_id), '--vm-disk-name', disk_name, '--turl', turl,
            '--register', '--mark', '--attach', '--link', '--op', 'up'
        ], 'Unable to attach persistent disk: %s, %s, %s, %s, %s' %
                     (diskSrc, vm_dir, str(vm_id), disk_name, turl))

    def _retrieveAndCachePDiskImage(self):
        self.manifestDownloader.downloadManifestByImageId(
            self.marketplaceImageId)
        self._validateMarketplaceImagePolicy()
        try:
            self._downloadImage()
            self._checkDownloadedImageChecksum()
            self._uploadDownloadedImageToPdisk()
        except:
            self._deletePDiskSnapshot()
            raise
        finally:
            try:
                self._deleteDownloadedImage()
            except:
                pass

    def _remotelyCachePDiskImage(self):
        """
        This function initializes a new persistent volume from a URL.  The image
        contents are downloaded directly from the URL by the persistent disk
        service.  The size (in bytes) and SHA-1 checksum are also validated.
        """

        self.manifestDownloader.downloadManifestByImageId(
            self.marketplaceImageId)
        self._validateMarketplaceImagePolicy()

        imageLocations = self.manifestDownloader.getImageLocations()
        self._assertLength(imageLocations, 1, atLeast=True)
        url = imageLocations[0]

        sizeInBytes = self._getImageSize()
        sha1 = self._getImageChecksum(self._CHECKSUM)

        gbBytes = 10**9
        sizeInGB = long(sizeInBytes) / gbBytes
        if long(sizeInBytes) % gbBytes > 0:
            sizeInGB += 1

        self.pdiskImageId = self.pdisk.createVolumeFromUrl(
            sizeInGB, '', False, url, str(sizeInBytes), sha1)

        self._setNewPDiskImageOriginProperties()

    def _uploadDownloadedImageToPdisk(self):
        volume_url = self.pdisk.uploadVolume(self.downloadedLocalImageLocation)
        self.pdiskImageId = volume_url.rsplit('/', 1)[1]
        self._setNewPDiskImageOriginProperties()

    def _setNewPDiskImageOriginProperties(self):
        self._setPDiskInfo(self._IDENTIFIER_KEY, self.marketplaceImageId,
                           self.pdiskImageId)
        self._setPDiskInfo(self._TYPE_KEY, 'MACHINE_IMAGE_ORIGIN',
                           self.pdiskImageId)

    def _getPDiskTempStore(self):
        store = self.configHolder.persistentDiskTempStore or '/tmp'
        self._sshDst(['mkdir', '-p', store],
                     'Unable to create temporary store')
        return store

    def _createPDiskSnapshot(self):
        snapshotIdentifier = 'snapshot:%s' % self.pdiskImageId
        self.pdiskSnapshotId = self.pdisk.createCowVolume(
            self.pdiskImageId, None)
        self._setPDiskIdentifier(snapshotIdentifier, self.pdiskSnapshotId)

    def _checkAuthorization(self):
        self.vmOwner = self._deriveVMOwner()
        disk_owner = self._getDiskOwner(self.pdiskImageId)
        disk_visibility = self._getDiskVisibility(self.pdiskImageId)
        if disk_owner not in [self.vmOwner, self._PDISK_SUPERUSER] and \
             disk_visibility in self._DISK_UNAUTHORIZED_VISIBILITIES:
            raise ValueError('User %s is not authorized to start image %s' % \
                             (self.vmOwner, self.marketplaceImageId))

    def _setSnapshotOwner(self):
        if not self.vmOwner:
            raise ValueError('VM owner is not set.')
        self.pdisk.updateVolume({'owner': self.vmOwner}, self.pdiskSnapshotId)

    def _setPDiskInfo(self, key, value, pdiskId):
        self.pdisk.updateVolume({key: value}, pdiskId)

    def _setPDiskIdentifier(self, value, pdiskId):
        self.pdisk.updateVolume({self._IDENTIFIER_KEY: value}, pdiskId)

    def _getPDiskSnapshotURL(self):
        return '%s/%s' % (self.pdiskEndpoint, self.pdiskSnapshotId)

    def _deletePDiskSnapshot(self, *args, **kwargs):
        if self.pdiskSnapshotId is None:
            return
        try:
            #FIXME: why do we need to set credentials here?
            self.pdisk._setPDiskUserCredentials()
            self.pdisk.deleteVolume(self.pdiskSnapshotId)
        except:
            pass

    # -------------------------------------------
    # Utility
    # -------------------------------------------

    def _removeExtension(self, filename):
        return '.'.join(filename.split('.')[:-1])

    def _getVirtualSizeBytesFromQemu(self, qemuOutput):
        for line in qemuOutput.split('\n'):
            if line.lstrip().startswith('virtual'):
                bytesAndOtherThings = line.split('(')
                self._assertLength(bytesAndOtherThings)
                bytesAndUnit = bytesAndOtherThings[1].split(' ')
                self._assertLength(bytesAndUnit)
                return int(bytesAndUnit[0])
        raise ValueError('Unable to find image bytes size')

    def _getDiskPath(self, arg):
        return self._getStringPart(arg, 1)

    def _getDiskHost(self, arg):
        return self._getStringPart(arg, 0)

    def _getStringPart(self, arg, part, nbPart=2, delimiter=':'):
        path = arg.split(delimiter)
        self._assertLength(path, nbPart)
        return path[part]

    def _findNumbers(self, elems):
        findedNb = []
        for nb in elems:
            try:
                findedNb.append(int(nb))
            except Exception:
                pass
        return findedNb

    def _getDiskIndex(self, diskPath):
        try:
            return int(diskPath.split('.')[-1])
        except:
            raise ValueError('Unable to determine disk index')

    def _assertLength(self, elements, length=2, errorMsg=None, atLeast=False):
        nbElem = len(elements)
        if not errorMsg:
            errorMsg = 'Object should have a length of %s%s , got %s\n%s' % (
                length, atLeast and ' at least' or '', nbElem, str(elements))
        if not atLeast and nbElem != length or nbElem < length:
            raise ValueError(errorMsg)

    def _bytesToGiga(self, bytesAmount):
        return (bytesAmount / 1024**3) + 1

    def _sshDst(self, cmd, errorMsg, dontRaiseOnError=False):
        retCode, output = sshCmdWithOutput(
            ' '.join(cmd),
            self.diskDstHost,
            user=getuser(),
            sshKey=sshPublicKeyLocation.replace('.pub', ''))
        if not dontRaiseOnError and retCode != 0:
            raise Exception('%s\n: Error: %s' % (errorMsg, output))
        return output

    def _sshPDisk(self, cmd, errorMsg, dontRaiseOnError=False):
        cmd_str = ' '.join(cmd)
        printStep("Executing: %s" % cmd_str)
        retCode, output = sshCmdWithOutput(
            cmd_str,
            self.pdisk.persistentDiskIp,
            user=getuser(),
            sshKey=self.pdisk.persistentDiskPrivateKey.replace('.pub', ''))
        if not dontRaiseOnError and retCode != 0:
            raise Exception('%s\n: Error: %s' % (errorMsg, output))
        return output

    def _getVMOwner(self, instanceId):
        credentials = LocalhostCredentialsConnector(self.configHolder)
        cloud = CloudConnectorFactory.getCloud(credentials)
        cloud.setEndpointFromParts('localhost', self.configHolder.onePort)
        return cloud.getVmOwner(instanceId)

    def _retrieveInstanceId(self):
        pathElems = self.diskDstPath.split('/')
        instanceId = self._findNumbers(pathElems)
        errorMsg = '%s instance ID in path. ' + 'Path is "%s"' % self.diskDstPath
        if len(instanceId) != 1:
            raise ValueError(
                errorMsg % ((len(instanceId) == 0) and 'Unable to find'
                            or 'Too many candidates'))
        return instanceId.pop()

    def _deriveVMOwner(self):
        instanceId = self._retrieveInstanceId()
        owner = self._getVMOwner(instanceId)
        return owner

    def _getDiskOwner(self, pdiskImageId):
        return self.pdisk.getValue(self._OWNER_KEY, pdiskImageId)

    def _getDiskVisibility(self, pdiskImageId):
        return self.pdisk.getValue(self._VISIBILITY_KEY, pdiskImageId)
Ejemplo n.º 2
0
class TMQuarantine(object):
    """Quarantine the files for a terminated virtual machine"""

    # Debug option
    PRINT_TRACE_ON_ERROR = True
    DEFAULT_VERBOSE_LEVEL = 0

    # Position of the provided args
    _ARG_SRC_POS = 1

    _PDISK_PORT = 8445

    def __init__(self, args, **kwargs):
        self.args = args

        self.diskSrcPath = None
        self.diskSrcHost = None
        self.vmDir = None
        self.diskName = None
        self.pdiskHostPort = None
        self.snapshotMarketplaceId = None
        self.targetMarketplace = None
        self.createdPDiskId = None
        self.p12cert = ''
        self.p12pswd = None
        self.pdiskEndpoint = None
        self.pdiskPath = None
        self.pdiskPathNew = None
        self.originImageIdUrl = None
        self.originImageId = None
        self.originMarketPlace = None
        self.instanceId = None
        self.cloud = None

        self.rootVolumeUuid = None

        self.persistentDiskIp = None
        self.persistentDiskLvmDevice = None

        self._initFromConfig(kwargs.get('conf_filename', ''))

        self._initCloudConnector()

    def run(self):
        try:
            self._run()
        finally:
            self._cleanup()

    def _run(self):
        self._checkArgs()
        self._parseArgs()
        self._retrieveInstanceId()
        self._retrieveVmDir()
        self._retrieveAttachedVolumeInfo()
        self._detachAllVolumes()
        self._changeOwnerOfSnapshotVolume()
        self._moveFilesToQuarantine()

    def _initFromConfig(self, conf_filename=''):
        config = ConfigHolder.configFileToDictWithFormattedKeys(conf_filename or
                                                                defaultConfigFile)
        options = PDiskEndpoint.options()
        self.configHolder = ConfigHolder(options, config)
        self.configHolder.set('pdiskEndpoint', self._createPdiskEndpoint())
        self.configHolder.set('verboseLevel', self.DEFAULT_VERBOSE_LEVEL)
        self.configHolder.assign(self)

    def _initCloudConnector(self):
        credentials = LocalhostCredentialsConnector(self.configHolder)
        self.cloud = CloudConnectorFactory.getCloud(credentials)
        self.cloud.setEndpointFromParts('localhost', self.configHolder.onePort)

    def _checkArgs(self):
        if len(self.args) != 2:
            raise ValueError('Invalid number of arguments')

    def _parseArgs(self):
        src = self.args[self._ARG_SRC_POS]
        self.diskSrcPath = self._getDiskPath(src)
        self.diskSrcHost = self._getDiskHost(src)

    # FIXME: duplicates should be pulled into common location
    def _createPdiskEndpoint(self):
        host = self.configHolder.persistentDiskIp
        port = self.configHolder.persistentDiskPort or _PDISK_PORT
        path = self.configHolder.persistentDiskPath or ''
        path = path.strip('/')
        return 'https://%s:%s/%s' % (host, port, path)

    def _changeOwnerOfSnapshotVolume(self):
        pdisk = VolumeManagerFactory.create(self.configHolder)

        # root volume may not exist, if this is an image creation
        # only actually change ownership of snapshot volumes
        if self.rootVolumeUuid:
            disk_identifier = pdisk.getValue('identifier', self.rootVolumeUuid)
            if re.match('.*snapshot.*', disk_identifier):
                pdisk.quarantineVolume(self.rootVolumeUuid)

    def _moveFilesToQuarantine(self):
        instance_dir = os.path.join(self.vmDir, str(self.instanceId))
        quarantine_dir = os.path.join(self.vmDir, 'quarantine')
        self._moveFilesToQuarantineLocal(instance_dir, quarantine_dir)
        self._moveFilesToQuarantineHypervisor(instance_dir, quarantine_dir)

    def _moveFilesToQuarantineLocal(self, instance_dir, quarantine_dir):
        shutil.move(instance_dir, quarantine_dir)

    def _moveFilesToQuarantineHypervisor(self, instance_dir, quarantine_dir):
        # If the storage area is on NFS and shared between the server
        # and the hypervisor, this will always fail.  Simply try to do
        # this and ignore errors if they arise, hoping that the error is
        # because the quarantine has already been done.
        self._sshDst(['mv', instance_dir, quarantine_dir],
                     'Failed to quarantine VM on hypervisor.', True)

    #--------------------------------------------
    # Persistent disk and related
    #--------------------------------------------

    def _retrieveAttachedVolumeInfo(self):
        uris = self._getAttachedVolumeURIs()
        self.attachedVolumeURIs = uris

    def _getAttachedVolumeURIs(self):
        register_filename_contents = self._sshDst(['/usr/sbin/stratus-list-registered-volumes.py',
                                                   '--vm-id', str(self.instanceId)],
                                                  'Unable to get registered volumes')
        return self._sanitizeVolumeURIs(register_filename_contents.splitlines())

    def _sanitizeVolumeURIs(self, volume_uris):
        "Filtering assumes that the disk's name is UUID."
        return filter(lambda x: is_uuid(self._getDiskNameFromURI(x.strip())),
                      volume_uris)

    def _getDiskNameFromURI(self, uri):
        return uri.strip().strip('/').split('/').pop()

    def _getPDiskHostPortFromURI(self, uri):
        splittedUri = uri.split(':')
        self._assertLength(splittedUri, 4)
        return ':'.join(splittedUri[1:3])

    def _detachAllVolumes(self):
        pdisk = VolumeManagerFactory.create(self.configHolder)

        msg = ''
        self.rootVolumeUuid = None
        for pdisk_uri in self.attachedVolumeURIs:

            pdisk_uri = pdisk_uri.strip()

            if pdisk_uri:
                # saves the root volume uuid so that the ownership can be changed later
                if not self.rootVolumeUuid:
                    self.rootVolumeUuid = self._getDiskNameFromURI(pdisk_uri)

                try:
                    self._detachSingleVolume(pdisk, pdisk_uri)
                except Exception as e:
                    msg += str(e) + "\n"

        if msg:
            raise Exception(msg)

    def _detachSingleVolume(self, pdisk, pdisk_uri):
        uuid = self._getDiskNameFromURI(pdisk_uri)
        turl = pdisk.getTurl(uuid)
        self._sshDst(['/usr/sbin/stratus-pdisk-client.py',
                      '--pdisk-id', pdisk_uri,
                      '--vm-id', str(self.instanceId),
                      '--turl', turl,
                      '--register', '--mark', '--attach', '--op', 'down'],
                     'Unable to detach pdisk "%s with TURL %s on VM %s"' %
                     (pdisk_uri, turl, str(self.instanceId)))

    #--------------------------------------------
    # Utility
    #--------------------------------------------

    def _assertLength(self, elem, size):
        if len(elem) != size:
            raise ValueError('List should have %s element(s), got %s' % (size, len(elem)))

    def _getDiskPath(self, arg):
        return self._getStringPart(arg, 1)

    def _getDiskHost(self, arg):
        return self._getStringPart(arg, 0)

    def _findNumbers(self, elems):
        findedNb = []
        for nb in elems:
            try:
                findedNb.append(int(nb))
            except Exception:
                pass
        return findedNb

    def _getStringPart(self, arg, part, nbPart=2, delimiter=':'):
        path = arg.split(delimiter)
        self._assertLength(path, nbPart)
        return path[part]

    def _retrieveInstanceId(self):
        pathElems = self.diskSrcPath.split('/')
        instanceId = self._findNumbers(pathElems)
        errorMsg = '%s instance ID in path. ' + 'Path is "%s"' % self.diskSrcPath
        if len(instanceId) != 1:
            raise ValueError(errorMsg % ((len(instanceId) == 0) and 'Unable to find'
                                         or 'Too many candidates'))
        self.instanceId = instanceId.pop()

    def _retrieveVmDir(self):
        self.vmDir = dirname(dirname(self.diskSrcPath))

    def _sshDst(self, cmd, errorMsg, dontRaiseOnError=False):
        return self._ssh(self.diskSrcHost, cmd, errorMsg, dontRaiseOnError)

    def _ssh(self, host, cmd, errorMsg, dontRaiseOnError=False):
        retCode, output = sshCmdWithOutput(' '.join(cmd), host, user=getuser(),
                                           sshKey=sshPublicKeyLocation.replace('.pub', ''))
        if not dontRaiseOnError and retCode != 0:
            raise Exception('%s\n: Error: %s' % (errorMsg, output))
        return output

    def _cleanup(self):
        pass