# Copyright IBM Corp. and contributors # # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Utilities related to the PowerVM management partition. The management partition is a special LPAR that runs the PowerVM REST API service. It itself appears through the REST API as a LogicalPartition of type aixlinux, but with the is_mgmt_partition property set to True. The PowerVM Nova Compute service runs on the management partition. """ import glob import os from nova import exception import nova.privsep.path from oslo_concurrency import lockutils from oslo_log import log as logging from pypowervm.tasks import partition as pvm_par import retrying from nova_powervm.virt.powervm import exception as npvmex LOG = logging.getLogger(__name__) _MP_UUID = None @lockutils.synchronized("mgmt_lpar_uuid") def mgmt_uuid(adapter): """Returns the management partitions UUID.""" global _MP_UUID if not _MP_UUID: _MP_UUID = pvm_par.get_this_partition(adapter).uuid return _MP_UUID 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 nova.privsep.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 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. nova.privsep.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) # Else stat raised - the device disappeared - all done.