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

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()