322 lines
13 KiB
Python
322 lines
13 KiB
Python
# Copyright 2015, 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 six
|
|
|
|
from oslo_log import log as logging
|
|
from pypowervm import exceptions as pvm_exc
|
|
from pypowervm.tasks import partition as pvm_partition
|
|
from pypowervm.tasks import storage as tsk_stg
|
|
from pypowervm.utils import transaction as pvm_tx
|
|
from pypowervm.wrappers import virtual_io_server as pvm_vios
|
|
|
|
from nova_powervm.virt.powervm import exception as exc
|
|
from nova_powervm.virt.powervm import vm
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
LOCAL_FEED_TASK = 'local_feed_task'
|
|
|
|
|
|
@six.add_metaclass(abc.ABCMeta)
|
|
class PowerVMVolumeAdapter(object):
|
|
"""The volume adapter connects a Cinder volume to a VM.
|
|
|
|
The role of the volume driver is to perform the connection between the
|
|
compute node and the backing physical fabric.
|
|
|
|
This volume adapter is a generic adapter for all volume types to extend.
|
|
|
|
This is built similarly to the LibvirtBaseVolumeDriver.
|
|
"""
|
|
def __init__(self, adapter, host_uuid, instance, connection_info,
|
|
stg_ftsk=None):
|
|
"""Initialize the PowerVMVolumeAdapter
|
|
|
|
:param adapter: The pypowervm adapter.
|
|
:param host_uuid: The pypowervm UUID of the host.
|
|
:param instance: The nova instance that the volume should connect to.
|
|
:param connection_info: The volume connection info generated from the
|
|
BDM. Used to determine how to connect the
|
|
volume to the VM.
|
|
:param stg_ftsk: (Optional) The pypowervm transaction FeedTask for the
|
|
I/O Operations. If provided, the Virtual I/O Server
|
|
mapping updates will be added to the FeedTask. This
|
|
defers the updates to some later point in time. If
|
|
the FeedTask is not provided, the updates will be run
|
|
immediately when the respective method is executed.
|
|
"""
|
|
self.adapter = adapter
|
|
self.host_uuid = host_uuid
|
|
self.instance = instance
|
|
self.connection_info = connection_info
|
|
self.vm_uuid = vm.get_pvm_uuid(instance)
|
|
# Lazy-set this
|
|
self._vm_id = None
|
|
|
|
self.reset_stg_ftsk(stg_ftsk=stg_ftsk)
|
|
|
|
@property
|
|
def vm_id(self):
|
|
"""Return the short ID (not UUID) of the LPAR for our instance.
|
|
|
|
This method is unavailable during a pre live migration call since
|
|
there is no instance of the VM on the destination host at the time.
|
|
"""
|
|
if self._vm_id is None:
|
|
self._vm_id = vm.get_vm_id(self.adapter, self.vm_uuid)
|
|
return self._vm_id
|
|
|
|
@property
|
|
def volume_id(self):
|
|
"""Method to return the volume id.
|
|
|
|
Every driver must implement this method if the default impl will
|
|
not work for their data.
|
|
"""
|
|
return self.connection_info['serial']
|
|
|
|
def reset_stg_ftsk(self, stg_ftsk=None):
|
|
"""Resets the pypowervm transaction FeedTask to a new value.
|
|
|
|
The previous updates from the original FeedTask WILL NOT be migrated
|
|
to this new FeedTask.
|
|
|
|
:param stg_ftsk: (Optional) The pypowervm transaction FeedTask for the
|
|
I/O Operations. If provided, the Virtual I/O Server
|
|
mapping updates will be added to the FeedTask. This
|
|
defers the updates to some later point in time. If
|
|
the FeedTask is not provided, the updates will be run
|
|
immediately when this method is executed.
|
|
"""
|
|
if stg_ftsk is None:
|
|
getter = pvm_vios.VIOS.getter(self.adapter, xag=self.min_xags())
|
|
self.stg_ftsk = pvm_tx.FeedTask(LOCAL_FEED_TASK, getter)
|
|
else:
|
|
self.stg_ftsk = stg_ftsk
|
|
|
|
@classmethod
|
|
def min_xags(cls):
|
|
"""List of pypowervm XAGs needed to support this adapter."""
|
|
raise NotImplementedError()
|
|
|
|
@property
|
|
def vios_uuids(self):
|
|
"""List the UUIDs of the Virtual I/O Servers hosting the storage."""
|
|
vios_wraps = pvm_partition.get_active_vioses(self.adapter)
|
|
return [wrap.uuid for wrap in vios_wraps]
|
|
|
|
@classmethod
|
|
def vol_type(cls):
|
|
"""The type of volume supported by this driver."""
|
|
raise NotImplementedError()
|
|
|
|
def is_volume_on_vios(self, vios_w):
|
|
"""Returns whether or not the volume is on VIOS.
|
|
|
|
This method is used in the NovaSlotManager to build slot map.
|
|
Needs to be implemented within the subclass to support rebuild.
|
|
|
|
:param vios_w: The Virtual I/O Server wrapper
|
|
:return: True if the volume is available on the VIOS. False
|
|
otherwise.
|
|
:return: The unique identification of the volume.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
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.
|
|
|
|
Certain volume connectors may need to pass data from the source host
|
|
to the target. This may be required to determine how volumes connect
|
|
through the Virtual I/O Servers.
|
|
|
|
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.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def pre_live_migration_on_source(self, mig_data):
|
|
"""Performs pre live migration steps for the volume on the source host.
|
|
|
|
Certain volume connectors may need to pass data from the source host
|
|
to the target. This may be required to determine how volumes connect
|
|
through the Virtual I/O Servers.
|
|
|
|
This method gives the volume connector an opportunity to update the
|
|
mig_data (a dictionary) with any data that is needed for the target
|
|
host during the pre-live migration step.
|
|
|
|
Since the source host has no native pre_live_migration step, this is
|
|
invoked from check_can_live_migrate_source in the overall live
|
|
migration flow.
|
|
|
|
:param mig_data: A dictionary that the method can update to include
|
|
data needed by the pre_live_migration_at_destination
|
|
method.
|
|
"""
|
|
pass
|
|
|
|
def post_live_migration_at_source(self, migrate_data):
|
|
"""Performs post live migration for the volume on the source host.
|
|
|
|
This method can be used to handle any steps that need to taken on
|
|
the source host after the VM is on the destination.
|
|
|
|
:param migrate_data: volume migration data
|
|
"""
|
|
pass
|
|
|
|
def post_live_migration_at_destination(self, mig_vol_stor):
|
|
"""Perform post live migration steps for the volume on the target host.
|
|
|
|
This method performs any post live migration that is needed. Is not
|
|
required to be implemented.
|
|
|
|
:param mig_vol_stor: An unbounded dictionary that will be passed to
|
|
each volume adapter during the post live migration
|
|
call. Adapters can store data in here that may
|
|
be used by subsequent volume adapters.
|
|
"""
|
|
pass
|
|
|
|
def cleanup_volume_at_destination(self, migrate_data):
|
|
"""Performs volume cleanup after LPM failure on the dest host.
|
|
|
|
This method can be used to handle any steps that need to taken on
|
|
the destination host after the migration has failed.
|
|
|
|
:param migrate_data: migration data
|
|
"""
|
|
pass
|
|
|
|
def connect_volume(self, slot_mgr):
|
|
"""Connects the volume.
|
|
|
|
:param slot_mgr: A NovaSlotManager. Used to store/retrieve the client
|
|
slots used when a volume is attached to the VM
|
|
"""
|
|
# Check if the VM is in a state where the attach is acceptable.
|
|
lpar_w = vm.get_instance_wrapper(self.adapter, self.instance)
|
|
capable, reason = lpar_w.can_modify_io()
|
|
if not capable:
|
|
raise exc.VolumeAttachFailed(
|
|
volume_id=self.volume_id, instance_name=self.instance.name,
|
|
reason=reason)
|
|
|
|
# Run the connect
|
|
self._connect_volume(slot_mgr)
|
|
|
|
if self.stg_ftsk.name == LOCAL_FEED_TASK:
|
|
self.stg_ftsk.execute()
|
|
|
|
def extend_volume(self):
|
|
raise NotImplementedError()
|
|
|
|
def _extend_volume(self, udid):
|
|
"""Rescan virtual disk so client VM can see extended size"""
|
|
resized = False
|
|
error = False
|
|
for vios_uuid in self.vios_uuids:
|
|
try:
|
|
LOG.debug("Rescanning volume %(vol)s for vios uuid %(uuid)s",
|
|
dict(vol=self.volume_id, uuid=vios_uuid),
|
|
instance=self.instance)
|
|
tsk_stg.rescan_vstor(vios_uuid, udid, adapter=self.adapter)
|
|
resized = True
|
|
except pvm_exc.VstorNotFound:
|
|
LOG.info("Failed to find volume %(vol)s for VIOS "
|
|
"UUID %(uuid)s during extend operation.",
|
|
{'vol': self.volume_id, 'uuid': vios_uuid},
|
|
instance=self.instance)
|
|
except pvm_exc.JobRequestFailed as e:
|
|
error = True
|
|
LOG.error("Failed to rescan volume %(vol)s for VIOS "
|
|
"UUID %(uuid)s. %(reason)s",
|
|
{'vol': self.volume_id, 'uuid': vios_uuid,
|
|
'reason': six.text_type(e)}, instance=self.instance)
|
|
if not resized or error:
|
|
raise exc.VolumeExtendFailed(volume_id=self.volume_id,
|
|
instance_name=self.instance.name)
|
|
|
|
def disconnect_volume(self, slot_mgr):
|
|
"""Disconnect the volume.
|
|
|
|
:param slot_mgr: A NovaSlotManager. Used to store/retrieve the client
|
|
slots used when a volume is detached from the VM.
|
|
"""
|
|
# Check if the VM is in a state where the detach is acceptable.
|
|
lpar_w = vm.get_instance_wrapper(self.adapter, self.instance)
|
|
capable, reason = lpar_w.can_modify_io()
|
|
if not capable:
|
|
raise exc.VolumeDetachFailed(
|
|
volume_id=self.volume_id, instance_name=self.instance.name,
|
|
reason=reason)
|
|
|
|
# Run the disconnect
|
|
self._disconnect_volume(slot_mgr)
|
|
|
|
if self.stg_ftsk.name == LOCAL_FEED_TASK:
|
|
self.stg_ftsk.execute()
|
|
|
|
def _connect_volume(self, slot_mgr):
|
|
"""Connects the volume.
|
|
|
|
This is the actual method to implement within the subclass. Some
|
|
transaction maintenance is done by the parent class.
|
|
|
|
:param slot_mgr: A NovaSlotStore. Used to store/retrieve the client
|
|
slots used when a volume is attached to the VM.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def _disconnect_volume(self, slot_mgr):
|
|
"""Disconnect the volume.
|
|
|
|
This is the actual method to implement within the subclass. Some
|
|
transaction maintenance is done by the parent class.
|
|
|
|
:param slot_mgr: A NovaSlotManager. Used to delete the client slots
|
|
used when a volume is detached from the VM
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
|
|
@six.add_metaclass(abc.ABCMeta)
|
|
class FibreChannelVolumeAdapter(PowerVMVolumeAdapter):
|
|
"""Defines a Fibre Channel specific volume adapter.
|
|
|
|
Fibre Channel has a few additional attributes for the volume adapter.
|
|
This class defines the additional attributes so that the multiple FC
|
|
sub classes can support them.
|
|
"""
|
|
|
|
def wwpns(self):
|
|
"""Builds the WWPNs of the adapters that will connect the ports.
|
|
|
|
:return: The list of WWPNs that need to be included in the zone set.
|
|
"""
|
|
raise NotImplementedError()
|