3bb59e393f
Add instance snapshot support for the PowerVM virt driver. Blueprint: powervm-snapshot Change-Id: I2691b09d95691915dc1065284d25ad22db41d32b
176 lines
7.4 KiB
Python
176 lines
7.4 KiB
Python
# Copyright 2015, 2018 IBM Corp.
|
|
#
|
|
# 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 os import 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 import exception
|
|
from nova.privsep import path as priv_path
|
|
|
|
|
|
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
|
|
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 exception.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 exception.UniqueDiskDiscoveryException(path_pattern=dpathpat,
|
|
count=len(disks))
|
|
|
|
# The by-id path is a symlink. Resolve to the /dev/sdX path
|
|
dpath = 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 = 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 exception.DeviceDeletionException(
|
|
devpath=devpath, polls=re.last_attempt.attempt_number,
|
|
timeout=scan_timeout)
|
|
# Else stat raised - the device disappeared - all done.
|