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)
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