nova-powervm/nova_powervm/virt/powervm/volume/fileio.py

183 lines
7.0 KiB
Python

# Copyright 2017 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.
import abc
import os
import six
from taskflow import task
from nova_powervm import conf as cfg
from nova_powervm.virt.powervm import exception as p_exc
from nova_powervm.virt.powervm import vm
from nova_powervm.virt.powervm.volume import driver as v_driver
from oslo_log import log as logging
from pypowervm import const as pvm_const
from pypowervm.tasks import client_storage as pvm_c_stor
from pypowervm.tasks import partition
from pypowervm.tasks import scsi_mapper as tsk_map
from pypowervm.wrappers import storage as pvm_stg
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
@six.add_metaclass(abc.ABCMeta)
class FileIOVolumeAdapter(v_driver.PowerVMVolumeAdapter):
"""Base class for connecting file based Cinder Volumes to PowerVM VMs."""
def __init__(self, adapter, host_uuid, instance, connection_info,
stg_ftsk=None):
super(FileIOVolumeAdapter, self).__init__(
adapter, host_uuid, instance, connection_info, stg_ftsk=stg_ftsk)
self._nl_vios_ids = None
@classmethod
def min_xags(cls):
return [pvm_const.XAG.VIO_SMAP]
@classmethod
def vol_type(cls):
"""The type of volume supported by this type."""
return 'fileio'
@abc.abstractmethod
def _get_path(self):
"""Return the path to the file to connect."""
pass
@property
def vios_uuids(self):
"""List the UUIDs of the Virtual I/O Servers hosting the storage."""
# Get the hosting UUID
if self._nl_vios_ids is None:
nl_vios_wrap = partition.get_mgmt_partition(self.adapter)
self._nl_vios_ids = [nl_vios_wrap.uuid]
return self._nl_vios_ids
def pre_live_migration_on_destination(self, mig_data):
"""Perform pre live migration steps for the volume on the target host.
This method performs any pre live migration that is needed.
This method will be called after the pre_live_migration_on_source
method. The data from the pre_live call will be passed in via the
mig_data. This method should put its output into the dest_mig_data.
:param mig_data: Dict of migration data for the destination server.
If the volume connector needs to provide
information to the live_migration command, it
should be added to this dictionary.
"""
LOG.debug("Incoming mig_data=%s", mig_data, instance=self.instance)
# Check if volume is available in destination.
vol_path = self._get_path()
if not os.path.exists(vol_path):
LOG.warning("File not found at path %s", vol_path,
instance=self.instance)
raise p_exc.VolumePreMigrationFailed(
volume_id=self.volume_id, instance_name=self.instance.name)
def _connect_volume(self, slot_mgr):
path = self._get_path()
# Get the File Path
fio = pvm_stg.FileIO.bld(
self.adapter, path,
backstore_type=pvm_stg.BackStoreType.FILE_IO)
def add_func(vios_w):
# If the vios doesn't match, just return
if vios_w.uuid not in self.vios_uuids:
return None
LOG.info("Adding logical volume disk connection to VIOS %(vios)s.",
{'vios': vios_w.name}, instance=self.instance)
slot, lua = slot_mgr.build_map.get_vscsi_slot(vios_w, path)
if slot_mgr.is_rebuild and not slot:
LOG.debug('Detected a device with path %(path)s on VIOS '
'%(vios)s on the rebuild that did not exist on the '
'source. Ignoring.',
{'path': path, 'vios': vios_w.uuid},
instance=self.instance)
return None
mapping = tsk_map.build_vscsi_mapping(
self.host_uuid, vios_w, self.vm_uuid, fio, lpar_slot_num=slot,
lua=lua)
return tsk_map.add_map(vios_w, mapping)
self.stg_ftsk.add_functor_subtask(add_func)
# Run after all the deferred tasks the query to save the slots in the
# slot map.
def set_slot_info():
vios_wraps = self.stg_ftsk.feed
partition_id = vm.get_vm_id(self.adapter, self.vm_uuid)
for vios_w in vios_wraps:
scsi_map = pvm_c_stor.udid_to_scsi_mapping(
vios_w, path, partition_id)
if not scsi_map:
continue
slot_mgr.register_vscsi_mapping(scsi_map)
self.stg_ftsk.add_post_execute(task.FunctorTask(
set_slot_info, name='file_io_slot_%s' % path))
def _disconnect_volume(self, slot_mgr):
# Build the match function
match_func = tsk_map.gen_match_func(pvm_stg.VDisk,
names=[self._get_path()])
# Make sure the remove function will run within the transaction manager
def rm_func(vios_w):
# If the vios doesn't match, just return
if vios_w.uuid not in self.vios_uuids:
return None
LOG.info("Disconnecting storage disks.", instance=self.instance)
removed_maps = tsk_map.remove_maps(vios_w, self.vm_uuid,
match_func=match_func)
for rm_map in removed_maps:
slot_mgr.drop_vscsi_mapping(rm_map)
return removed_maps
self.stg_ftsk.add_functor_subtask(rm_func)
# Find the disk directly.
vios_w = self.stg_ftsk.wrapper_tasks[self.vios_uuids[0]].wrapper
mappings = tsk_map.find_maps(vios_w.scsi_mappings,
client_lpar_id=self.vm_uuid,
match_func=match_func)
return [x.backing_storage for x in mappings]
def is_volume_on_vios(self, vios_w):
"""Returns whether or not the volume file is on a VIOS.
This method is used during live-migration and rebuild to
check if the volume is available on the target host.
:param vios_w: The Virtual I/O Server wrapper.
:return: True if the file is on the VIOS. False
otherwise.
:return: The file path.
"""
if vios_w.uuid not in self.vios_uuids:
return False, None
vol_path = self._get_path()
vol_found = os.path.exists(vol_path)
return vol_found, vol_path