def remove_block_dev(devpath, scan_timeout=10): """Remove a block device from the management partition. This method causes the operating system of the management partition to delete the device special files associated with the specified block device. :param devpath: Any path to the block special file associated with the device to be removed. :param scan_timeout: The maximum number of seconds after scanning to wait for the specified device to disappear. :raise InvalidDevicePath: If the specified device or its 'delete' special file cannot be found. :raise DeviceDeletionException: If the deletion was attempted, but the device special file is still present afterward. """ # Resolve symlinks, if any, to get to the /dev/sdX path devpath = os.path.realpath(devpath) try: os.stat(devpath) except OSError: raise exception.InvalidDevicePath(path=devpath) devname = devpath.rsplit('/', 1)[-1] delpath = '/sys/block/%s/device/delete' % devname try: os.stat(delpath) except OSError: raise exception.InvalidDevicePath(path=delpath) LOG.debug( "Deleting block device %(devpath)s from the management " "partition via special file %(delpath)s.", { 'devpath': devpath, 'delpath': delpath }) # Writing '1' to this sysfs file deletes the block device and rescans. priv_path.writefile(delpath, 'a', '1') # The bus scan is asynchronous. Need to poll, waiting for the device to # disappear. Stop when stat raises OSError (dev file not found) - which is # success - or after the specified timeout (which is failure). Sleep 1/4 # second between polls. @retrying.retry(retry_on_result=lambda result: result, wait_fixed=250, stop_max_delay=scan_timeout * 1000) def _poll_for_del(statpath): try: os.stat(statpath) return True except OSError: # Device special file is absent, as expected return False try: _poll_for_del(devpath) except retrying.RetryError as re: # stat just kept returning (dev file continued to exist). raise npvmex.DeviceDeletionException( devpath=devpath, polls=re.last_attempt.attempt_number, timeout=scan_timeout)
def discover_vscsi_disk(mapping, scan_timeout=300): """Bring a mapped device into the management partition and find its name. Based on a VSCSIMapping, scan the appropriate virtual SCSI host bus, causing the operating system to discover the mapped device. Find and return the path of the newly-discovered device based on its UDID in the mapping. Note: scanning the bus will cause the operating system to discover *all* devices on that bus. However, this method will only return the path for the specific device from the input mapping, based on its UDID. :param mapping: The pypowervm.wrappers.virtual_io_server.VSCSIMapping representing the mapping of the desired disk to the management partition. :param scan_timeout: The maximum number of seconds after scanning to wait for the specified device to appear. :return: The udev-generated ("/dev/sdX") name of the discovered disk. :raise NoDiskDiscoveryException: If the disk did not appear after the specified timeout. :raise UniqueDiskDiscoveryException: If more than one disk appears with the expected UDID. """ # Calculate the Linux slot number from the client adapter slot number. lslot = 0x30000000 | mapping.client_adapter.lpar_slot_num # We'll match the device ID based on the UDID, which is actually the last # 32 chars of the field we get from PowerVM. udid = mapping.backing_storage.udid[-32:] LOG.debug( "Trying to discover VSCSI disk with UDID %(udid)s on slot " "%(slot)x.", { 'udid': udid, 'slot': lslot }) # Find the special file to scan the bus, and scan it. # This glob should yield exactly one result, but use the loop just in case. for scanpath in glob.glob( '/sys/bus/vio/devices/%x/host*/scsi_host/host*/scan' % lslot): # Writing '- - -' to this sysfs file triggers bus rescan priv_path.writefile(scanpath, 'a', '- - -') # Now see if our device showed up. If so, we can reliably match it based # on its Linux ID, which ends with the disk's UDID. dpathpat = '/dev/disk/by-id/*%s' % udid # The bus scan is asynchronous. Need to poll, waiting for the device to # spring into existence. Stop when glob finds at least one device, or # after the specified timeout. Sleep 1/4 second between polls. @retrying.retry(retry_on_result=lambda result: not result, wait_fixed=250, stop_max_delay=scan_timeout * 1000) def _poll_for_dev(globpat): return glob.glob(globpat) try: disks = _poll_for_dev(dpathpat) except retrying.RetryError as re: raise npvmex.NoDiskDiscoveryException( bus=lslot, udid=udid, polls=re.last_attempt.attempt_number, timeout=scan_timeout) # If we get here, _poll_for_dev returned a nonempty list. If not exactly # one entry, this is an error. if len(disks) != 1: raise npvmex.UniqueDiskDiscoveryException(path_pattern=dpathpat, count=len(disks)) # The by-id path is a symlink. Resolve to the /dev/sdX path dpath = os.path.realpath(disks[0]) LOG.debug( "Discovered VSCSI disk with UDID %(udid)s on slot %(slot)x at " "path %(devname)s.", { 'udid': udid, 'slot': lslot, 'devname': dpath }) return dpath