nova-zvm-virt-driver/nova_zvm/virt/zvm/volumeop.py

1089 lines
45 KiB
Python

# Copyright 2013 IBM Corp.
#
# 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 contextlib
import re
import six
import time
import nova.context
from nova.i18n import _, _LW
from nova.objects import block_device as block_device_obj
from nova.objects import instance as instance_obj
from nova.volume import cinder
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import excutils
from nova_zvm.virt.zvm import const
from nova_zvm.virt.zvm import dist
from nova_zvm.virt.zvm import exception
from nova_zvm.virt.zvm import utils as zvmutils
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class VolumeOperator(object):
"""Volume operator on IBM z/VM platform."""
_svc_driver_obj = None
def __init__(self):
if not VolumeOperator._svc_driver_obj:
VolumeOperator._svc_driver_obj = SVCDriver()
self._svc_driver = VolumeOperator._svc_driver_obj
def init_host(self, host_stats):
try:
self._svc_driver.init_host(host_stats)
except (exception.ZVMDriverError, exception.ZVMVolumeError) as err:
LOG.warning(_LW("Initialize zhcp failed. Reason: %s"),
err.format_message())
def attach_volume_to_instance(self, context, connection_info, instance,
mountpoint, is_active, rollback=True):
"""Attach a volume to an instance."""
if not connection_info:
errmsg = _("Missing required parameters: connection_info.")
raise exception.ZVMDriverError(msg=errmsg)
if not instance:
errmsg = _("Missing required parameters: instance.")
raise exception.ZVMDriverError(msg=errmsg)
LOG.debug("Attach a volume to an instance. conn_info: %(info)s; " +
"instance: %(name)s; mountpoint: %(point)s",
{'info': connection_info, 'name': instance['name'],
'point': mountpoint})
if is_active:
self._svc_driver.attach_volume_active(context, connection_info,
instance, mountpoint,
rollback)
else:
self._svc_driver.attach_volume_inactive(context, connection_info,
instance, mountpoint,
rollback)
def detach_volume_from_instance(self, connection_info, instance,
mountpoint, is_active, rollback=True):
"""Detach a volume from an instance."""
if not connection_info:
errmsg = _("Missing required parameters: connection_info.")
raise exception.ZVMDriverError(msg=errmsg)
if not instance:
errmsg = _("Missing required parameters: instance.")
raise exception.ZVMDriverError(msg=errmsg)
LOG.debug("Detach a volume from an instance. conn_info: %(info)s; " +
"instance: %(name)s; mountpoint: %(point)s",
{'info': connection_info, 'name': instance['name'],
'point': mountpoint})
if is_active:
self._svc_driver.detach_volume_active(connection_info, instance,
mountpoint, rollback)
else:
self._svc_driver.detach_volume_inactive(connection_info, instance,
mountpoint, rollback)
def get_volume_connector(self, instance):
if not instance:
errmsg = _("Instance must be provided.")
raise exception.ZVMDriverError(msg=errmsg)
return self._svc_driver.get_volume_connector(instance)
def has_persistent_volume(self, instance):
if not instance:
errmsg = _("Instance must be provided.")
raise exception.ZVMDriverError(msg=errmsg)
return self._svc_driver.has_persistent_volume(instance)
def extract_connection_info(self, context, connection_info):
if not connection_info:
errmsg = _("Connection_info must be provided.")
raise exception.ZVMDriverError(msg=errmsg)
return self._svc_driver._extract_connection_info(context,
connection_info)
def get_root_volume_connection_info(self, bdm_list, root_device):
if not bdm_list:
errmsg = _("Block_device_mappings must be provided.")
raise exception.ZVMDriverError(msg=errmsg)
if not root_device:
errmsg = _("root_device must be provided.")
raise exception.ZVMDriverError(msg=errmsg)
for bdm in bdm_list:
if zvmutils.is_volume_root(bdm['mount_device'], root_device):
return bdm['connection_info']
errmsg = _("Failed to get connection info of root volume.")
raise exception.ZVMDriverError(msg=errmsg)
def volume_boot_init(self, instance, fcp):
if not instance:
errmsg = _("instance must be provided.")
raise exception.ZVMDriverError(msg=errmsg)
if not fcp:
errmsg = _("fcp must be provided.")
raise exception.ZVMDriverError(msg=errmsg)
self._svc_driver.volume_boot_init(instance, fcp)
def volume_boot_cleanup(self, instance, fcp):
if not instance:
errmsg = _("instance must be provided.")
raise exception.ZVMDriverError(msg=errmsg)
if not fcp:
errmsg = _("fcp must be provided.")
raise exception.ZVMDriverError(msg=errmsg)
self._svc_driver.volume_boot_cleanup(instance, fcp)
@contextlib.contextmanager
def wrap_internal_errors():
"""Wrap internal exceptions to ZVMVolumeError."""
try:
yield
except exception.ZVMBaseException:
raise
except Exception as err:
raise exception.ZVMVolumeError(msg=err)
class DriverAPI(object):
"""DriverAPI for implement volume_attach on IBM z/VM platform."""
def init_host(self, host_stats):
"""Initialize host environment."""
raise NotImplementedError
def get_volume_connector(self, instance):
"""Get volume connector for current driver."""
raise NotImplementedError
def attach_volume_active(self, context, connection_info, instance,
mountpoint, rollback):
"""Attach a volume to an running instance."""
raise NotImplementedError
def detach_volume_active(self, connection_info, instance, mountpoint,
rollback):
"""Detach a volume from an running instance."""
raise NotImplementedError
def attach_volume_inactive(self, context, connection_info, instance,
mountpoint, rollback):
"""Attach a volume to an shutdown instance."""
raise NotImplementedError
def detach_volume_inactive(self, connection_info, instance, mountpoint,
rollback):
"""Detach a volume from an shutdown instance."""
raise NotImplementedError
def has_persistent_volume(self, instance):
"""Decide if the specified instance has persistent volumes attached."""
raise NotImplementedError
class SVCDriver(DriverAPI):
"""SVC volume operator on IBM z/VM platform."""
class FCP(object):
"""FCP adapter class."""
_DEV_NO_PATTERN = '[0-9a-f]{1,4}'
_WWPN_PATTERN = '[0-9a-f]{16}'
_CHPID_PATTERN = '[0-9A-F]{2}'
def __init__(self, init_info):
"""Initialize a FCP device object from several lines of string
describing properties of the FCP device.
Here is a sample:
opnstk1: FCP device number: B83D
opnstk1: Status: Free
opnstk1: NPIV world wide port number: NONE
opnstk1: Channel path ID: 59
opnstk1: Physical world wide port number: 20076D8500005181
The format comes from the response of xCAT, do not support
arbitrary format.
"""
self._dev_no = None
self._npiv_port = None
self._chpid = None
self._physical_port = None
self._is_valid = True
# Sometimes nova issues get_volume_connector() for some reasons,
# which requires a FCP device being assigned to the instance. But
# nova will not attach any volume to the instance later. In this
# case we need to release the FCP device in later time. So a FCP
# device which is assigned to an instance doesn't means it's
# actually used by the instance.
self._is_reserved = False
self._is_in_use = False
self._reserve_time = 0
if isinstance(init_info, list) and (len(init_info) == 5):
self._dev_no = self._get_dev_number_from_line(init_info[0])
self._npiv_port = self._get_wwpn_from_line(init_info[2])
self._chpid = self._get_chpid_from_line(init_info[3])
self._physical_port = self._get_wwpn_from_line(init_info[4])
self._validate_device()
def _get_wwpn_from_line(self, info_line):
wwpn = info_line.split(':')[-1].strip().lower()
return wwpn if (wwpn and wwpn.upper() != 'NONE') else None
def _get_dev_number_from_line(self, info_line):
dev_no = info_line.split(':')[-1].strip().lower()
return dev_no if dev_no else None
def _get_chpid_from_line(self, info_line):
chpid = info_line.split(':')[-1].strip().upper()
return chpid if chpid else None
def _validate_device(self):
if not (self._dev_no and self._chpid):
self._is_valid = False
return
if not (self._npiv_port or self._physical_port):
self._is_valid = False
return
if not (re.match(self._DEV_NO_PATTERN, self._dev_no) and
re.match(self._CHPID_PATTERN, self._chpid)):
self._is_valid = False
return
if self._npiv_port and not re.match(self._WWPN_PATTERN,
self._npiv_port):
self._is_valid = False
return
if self._physical_port and not re.match(self._WWPN_PATTERN,
self._physical_port):
self._is_valid = False
return
def is_valid(self):
return self._is_valid
def get_dev_no(self):
return self._dev_no
def get_npiv_port(self):
return self._npiv_port
def get_chpid(self):
return self._chpid
def get_physical_port(self):
return self._physical_port
def reserve_device(self):
self._is_reserved = True
self._is_in_use = False
self._reserve_time = time.time()
def is_reserved(self):
return self._is_reserved
def set_in_use(self):
self._is_reserved = True
self._is_in_use = True
def is_in_use(self):
return self._is_in_use
def release_device(self):
self._is_reserved = False
self._is_in_use = False
self._reserve_time = 0
def get_reserve_time(self):
return self._reserve_time
_RESERVE = 0
_INCREASE = 1
_DECREASE = 2
_REMOVE = 3
_FORCE_REMOVE = 4
def __init__(self):
self._xcat_url = zvmutils.get_xcat_url()
self._path_utils = zvmutils.PathUtils()
self._host = CONF.zvm_host
self._pool_name = CONF.zvm_scsi_pool
self._fcp_pool = {}
self._instance_fcp_map = {}
self._is_instance_fcp_map_locked = False
self._volume_api = cinder.API()
self._dist_manager = dist.ListDistManager()
self._actions = {'attach_volume': 'addScsiVolume',
'detach_volume': 'removeScsiVolume',
'create_mountpoint': 'createfilesysnode',
'remove_mountpoint': 'removefilesysnode'}
def init_host(self, host_stats):
"""Initialize host environment."""
if not host_stats:
errmsg = _("Can not obtain host stats.")
raise exception.ZVMDriverError(msg=errmsg)
zhcp_fcp_list = CONF.zvm_zhcp_fcp_list
fcp_devices = self._expand_fcp_list(zhcp_fcp_list)
hcpnode = host_stats[0]['zhcp']['nodename']
for _fcp in fcp_devices:
with zvmutils.ignore_errors():
self._attach_device(hcpnode, _fcp)
with zvmutils.ignore_errors():
self._online_device(hcpnode, _fcp)
fcp_list = CONF.zvm_fcp_list
if (fcp_list is None):
errmsg = _("At least one fcp list should be given")
LOG.warning(errmsg)
raise exception.ZVMVolumeError(msg=errmsg)
self._init_fcp_pool(fcp_list)
self._init_instance_fcp_map(fcp_list)
def _init_fcp_pool(self, fcp_list):
"""The FCP infomation looks like this:
host: FCP device number: xxxx
host: Status: Active
host: NPIV world wide port number: xxxxxxxx
host: Channel path ID: xx
host: Physical world wide port number: xxxxxxxx
......
host: FCP device number: xxxx
host: Status: Active
host: NPIV world wide port number: xxxxxxxx
host: Channel path ID: xx
host: Physical world wide port number: xxxxxxxx
"""
complete_fcp_set = self._expand_fcp_list(fcp_list)
fcp_info = self._get_all_fcp_info()
lines_per_item = 5
num_fcps = len(fcp_info) / lines_per_item
for n in range(0, num_fcps):
fcp_init_info = fcp_info[(5 * n):(5 * (n + 1))]
fcp = SVCDriver.FCP(fcp_init_info)
dev_no = fcp.get_dev_no()
if dev_no in complete_fcp_set:
if fcp.is_valid():
self._fcp_pool[dev_no] = fcp
else:
errmsg = _("Find an invalid FCP device with properties {"
"dev_no: %(dev_no)s, "
"NPIV_port: %(NPIV_port)s, "
"CHPID: %(CHPID)s, "
"physical_port: %(physical_port)s} !") % {
'dev_no': fcp.get_dev_no(),
'NPIV_port': fcp.get_npiv_port(),
'CHPID': fcp.get_chpid(),
'physical_port': fcp.get_physical_port()}
LOG.warning(errmsg)
def _get_all_fcp_info(self):
fcp_info = []
free_fcp_info = self._list_fcp_details('free')
active_fcp_info = self._list_fcp_details('active')
if free_fcp_info:
fcp_info.extend(free_fcp_info)
if active_fcp_info:
fcp_info.extend(active_fcp_info)
return fcp_info
def _init_instance_fcp_map(self, fcp_list):
"""Map all instances and their fcp devices.
One instance should use only one fcp device when multipath
is not enabled.
"""
complete_fcp_set = self._expand_fcp_list(fcp_list)
# All other functions should not modify _instance_fcp_map during
# FCP pool initialization
self._is_instance_fcp_map_locked = True
compute_host_bdms = self._get_host_volume_bdms()
for instance_bdms in compute_host_bdms:
instance_name = instance_bdms['instance']['name']
for _bdm in instance_bdms['instance_bdms']:
connection_info = self._build_connection_info(_bdm)
try:
# Remove invalid FCP devices
fcp_list = connection_info['data']['zvm_fcp']
invalid_fcp_list = []
for fcp in fcp_list:
if fcp not in complete_fcp_set:
errmsg = _("FCP device %(dev)s is not configured "
"but is used by %(inst_name)s.") % {
'dev': fcp, 'inst_name': instance_name}
LOG.warning(errmsg)
invalid_fcp_list.append(fcp)
for fcp in invalid_fcp_list:
fcp_list.remove(fcp)
# Map valid FCP devices
if fcp_list:
self._update_instance_fcp_map(instance_name, fcp_list,
self._INCREASE)
except (TypeError, KeyError):
pass
self._is_instance_fcp_map_locked = False
def _update_instance_fcp_map_if_unlocked(self, instance_name, fcp_list,
action):
while self._is_instance_fcp_map_locked:
time.sleep(1)
self._update_instance_fcp_map(instance_name, fcp_list, action)
def _update_instance_fcp_map(self, instance_name, fcp_list, action):
if instance_name in self._instance_fcp_map:
current_list = self._instance_fcp_map[instance_name]['fcp_list']
if fcp_list and (fcp_list != current_list):
errmsg = _("FCP conflict for instance %(ins_name)s! "
"Original set: %(origin)s, new set: %(new)s"
) % {'ins_name': instance_name,
'origin': current_list, 'new': fcp_list}
raise exception.ZVMVolumeError(msg=errmsg)
if action == self._RESERVE:
if instance_name in self._instance_fcp_map:
count = self._instance_fcp_map[instance_name]['count']
if count > 0:
errmsg = _("Try to reserve fcp devices on which there are "
"already volumes attached: "
"%(ins_name)s:%(fcp_list)s") % {
'ins_name': instance_name,
'fcp_list': fcp_list}
raise exception.ZVMVolumeError(msg=errmsg)
else:
for fcp_no in fcp_list:
fcp = self._fcp_pool.get(fcp_no)
fcp.reserve_device()
new_item = {instance_name: {'fcp_list': fcp_list, 'count': 0}}
self._instance_fcp_map.update(new_item)
elif action == self._INCREASE:
for fcp_no in fcp_list:
fcp = self._fcp_pool.get(fcp_no)
fcp.set_in_use()
if instance_name in self._instance_fcp_map:
count = self._instance_fcp_map[instance_name]['count']
new_item = {instance_name: {'fcp_list': fcp_list,
'count': count + 1}}
self._instance_fcp_map.update(new_item)
else:
new_item = {instance_name: {'fcp_list': fcp_list, 'count': 1}}
self._instance_fcp_map.update(new_item)
elif action == self._DECREASE:
if instance_name in self._instance_fcp_map:
count = self._instance_fcp_map[instance_name]['count']
if count > 0:
new_item = {instance_name: {'fcp_list': fcp_list,
'count': count - 1}}
self._instance_fcp_map.update(new_item)
if count == 1:
for fcp_no in fcp_list:
fcp = self._fcp_pool.get(fcp_no)
# The function 'reserve_device()' will do two jobs.
# The first one is to cancel the 'in_use' status of
# the FCP, so function _is_fcp_in_use() can return
# right result. The second one is to facilitate
# rollback process in case the FCP being assigned
# to another instance after it's released.
fcp.reserve_device()
else:
errmsg = _("Reference count falling down below 0 for map "
"item: %(ins_name)s:%(fcp_list)s") % {
'ins_name': instance_name,
'fcp_list': fcp_list}
raise exception.ZVMVolumeError(msg=errmsg)
else:
errmsg = _("Try to decrease an inexistent map item: "
"%(ins_name)s:%(fcp_list)s"
) % {'ins_name': instance_name,
'fcp_list': fcp_list}
raise exception.ZVMVolumeError(msg=errmsg)
elif action == self._REMOVE:
if instance_name in self._instance_fcp_map:
count = self._instance_fcp_map[instance_name]['count']
if count > 0:
errmsg = _("Try to remove a map item with volumes "
"attached on: %(ins_name)s:%(fcp_list)s"
) % {'ins_name': instance_name,
'fcp_list': fcp_list}
raise exception.ZVMVolumeError(msg=errmsg)
else:
for fcp_no in fcp_list:
fcp = self._fcp_pool.get(fcp_no)
fcp.release_device()
self._instance_fcp_map.pop(instance_name)
else:
errmsg = _("Try to remove an inexistent map item: "
"%(ins_name)s:%(fcp_list)s"
) % {'ins_name': instance_name,
'fcp_list': fcp_list}
raise exception.ZVMVolumeError(msg=errmsg)
elif action == self._FORCE_REMOVE:
if instance_name in self._instance_fcp_map:
for fcp_no in fcp_list:
fcp = self._fcp_pool.get(fcp_no)
fcp.release_device()
self._instance_fcp_map.pop(instance_name)
else:
errmsg = _("Unrecognized option: %s") % action
raise exception.ZVMVolumeError(msg=errmsg)
def _get_host_volume_bdms(self):
"""Return all block device mappings on a compute host."""
compute_host_bdms = []
instances = self._get_all_instances()
for instance in instances:
instance_bdms = self._get_instance_bdms(instance)
compute_host_bdms.append(dict(instance=instance,
instance_bdms=instance_bdms))
return compute_host_bdms
def _get_all_instances(self):
context = nova.context.get_admin_context()
return instance_obj.InstanceList.get_by_host(context, self._host)
def _get_instance_bdms(self, instance):
context = nova.context.get_admin_context()
instance_bdms = [bdm for bdm in
(block_device_obj.BlockDeviceMappingList.
get_by_instance_uuid(context, instance['uuid']))
if bdm.is_volume]
return instance_bdms
def has_persistent_volume(self, instance):
return bool(self._get_instance_bdms(instance))
def _build_connection_info(self, bdm):
try:
connection_info = jsonutils.loads(bdm['connection_info'])
except (TypeError, KeyError, ValueError):
return None
# The value of zvm_fcp is a string in former release. It will be set
# in future. We have to translate a string FCP to a single-element set
# in order to make code goes on.
fcp = connection_info['data']['zvm_fcp']
if fcp and isinstance(fcp, six.string_types):
connection_info['data']['zvm_fcp'] = [fcp]
return connection_info
def get_volume_connector(self, instance):
empty_connector = {'zvm_fcp': [], 'wwpns': [], 'host': ''}
try:
fcp_list = self._instance_fcp_map.get(instance['name'])['fcp_list']
except Exception:
fcp_list = []
if not fcp_list:
fcp_list = self._get_fcp_from_pool()
if fcp_list:
self._update_instance_fcp_map_if_unlocked(
instance['name'], fcp_list, self._RESERVE)
if not fcp_list:
errmsg = _("No available FCP device found.")
LOG.warning(errmsg)
return empty_connector
wwpns = []
for fcp_no in fcp_list:
wwpn = self._get_wwpn(fcp_no)
if not wwpn:
errmsg = _("FCP device %s has no available WWPN.") % fcp_no
LOG.warning(errmsg)
else:
wwpns.append(wwpn)
if not wwpns:
errmsg = _("No available WWPN found.")
LOG.warning(errmsg)
return empty_connector
return {'zvm_fcp': fcp_list, 'wwpns': wwpns, 'host': CONF.zvm_host}
def _get_wwpn(self, fcp_no):
fcp = self._fcp_pool.get(fcp_no)
if not fcp:
return None
if fcp.get_npiv_port():
return fcp.get_npiv_port()
if fcp.get_physical_port():
return fcp.get_physical_port()
return None
def _list_fcp_details(self, state):
fields = '&field=--fcpdevices&field=' + state + '&field=details'
rsp = self._xcat_rinv(fields)
try:
fcp_details = rsp['info'][0][0].splitlines()
return fcp_details
except (TypeError, KeyError):
return None
def _get_fcp_from_pool(self):
fcp_list = []
for fcp in list(self._fcp_pool.values()):
if not fcp.is_reserved():
fcp_list.append(fcp.get_dev_no())
break
if not fcp_list:
self._release_fcps_reserved()
for fcp in list(self._fcp_pool.values()):
if not fcp.is_reserved():
fcp_list.append(fcp.get_dev_no())
break
if not fcp_list:
return []
if not CONF.zvm_multiple_fcp:
return fcp_list
primary_fcp = self._fcp_pool.get(fcp_list.pop())
backup_fcp = None
for fcp in list(self._fcp_pool.values()):
if not fcp.is_reserved() and (
fcp.get_chpid() != primary_fcp.get_chpid()):
backup_fcp = fcp
break
fcp_list.append(primary_fcp.get_dev_no())
if backup_fcp:
fcp_list.append(backup_fcp.get_dev_no())
else:
errmsg = _("Can not find a backup FCP device for primary FCP "
"device %s") % primary_fcp.get_dev_no()
LOG.warning(errmsg)
return []
return fcp_list
def _release_fcps_reserved(self):
current = time.time()
for instance in list(self._instance_fcp_map.keys()):
if self._instance_fcp_map.get(instance)['count'] != 0:
continue
# Only release FCP devices which are reserved more than 30 secs
# in case concurrent assignment.
fcp_list = self._instance_fcp_map.get(instance)['fcp_list']
fcp = self._fcp_pool.get(fcp_list[0])
if current - fcp.get_reserve_time() > 30:
self._update_instance_fcp_map_if_unlocked(instance, fcp_list,
self._REMOVE)
def _extract_connection_info(self, context, connection_info):
with wrap_internal_errors():
LOG.debug("Extract connection_info: %s", connection_info)
lun = connection_info['data']['target_lun']
lun = "%04x000000000000" % int(lun)
wwpn = connection_info['data']['target_wwn']
size = '0G'
# There is no context in detach case
if context:
volume_id = connection_info['data']['volume_id']
volume = self._get_volume_by_id(context, volume_id)
size = str(volume['size']) + 'G'
fcp_list = connection_info['data']['zvm_fcp']
return (lun.lower(), self._format_wwpn(wwpn), size,
self._format_fcp_list(fcp_list))
def _format_wwpn(self, wwpn):
if isinstance(wwpn, six.string_types):
return wwpn.lower()
else:
new_wwpn = ';'.join(wwpn)
return new_wwpn.lower()
def _format_fcp_list(self, fcp_list):
if len(fcp_list) == 1:
return fcp_list[0].lower()
else:
return ';'.join(fcp_list).lower()
def _get_volume_by_id(self, context, volume_id):
volume = self._volume_api.get(context, volume_id)
return volume
def attach_volume_active(self, context, connection_info, instance,
mountpoint, rollback=True):
"""Attach a volume to an running instance."""
(lun, wwpn, size, fcp) = self._extract_connection_info(context,
connection_info)
fcp_list = fcp.split(';')
self._update_instance_fcp_map_if_unlocked(instance['name'],
fcp_list, self._INCREASE)
try:
self._add_zfcp_to_pool(fcp, wwpn, lun, size)
self._add_zfcp(instance, fcp, wwpn, lun, size)
if mountpoint:
self._create_mountpoint(instance, fcp, wwpn, lun, mountpoint)
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError,
exception.ZVMVolumeError):
with excutils.save_and_reraise_exception():
self._update_instance_fcp_map_if_unlocked(instance['name'],
fcp_list,
self._DECREASE)
do_detach = not self._is_fcp_in_use(instance)
if rollback:
with zvmutils.ignore_errors():
self._remove_mountpoint(instance, mountpoint)
with zvmutils.ignore_errors():
self._remove_zfcp(instance, fcp, wwpn, lun)
with zvmutils.ignore_errors():
self._remove_zfcp_from_pool(wwpn, lun)
with zvmutils.ignore_errors():
if do_detach:
for dev_no in fcp_list:
self._detach_device(instance['name'], dev_no)
self._update_instance_fcp_map_if_unlocked(
instance['name'], fcp_list, self._REMOVE)
def detach_volume_active(self, connection_info, instance, mountpoint,
rollback=True):
"""Detach a volume from an running instance."""
(lun, wwpn, size, fcp) = self._extract_connection_info(None,
connection_info)
fcp_list = fcp.split(';')
self._update_instance_fcp_map_if_unlocked(instance['name'], fcp_list,
self._DECREASE)
try:
do_detach = not self._is_fcp_in_use(instance)
if mountpoint:
self._remove_mountpoint(instance, mountpoint)
self._remove_zfcp(instance, fcp, wwpn, lun)
self._remove_zfcp_from_pool(wwpn, lun)
if do_detach:
for dev_no in fcp_list:
self._detach_device(instance['name'], dev_no)
self._update_instance_fcp_map_if_unlocked(instance['name'],
fcp_list,
self._REMOVE)
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError,
exception.ZVMVolumeError):
with excutils.save_and_reraise_exception():
self._update_instance_fcp_map_if_unlocked(instance['name'],
fcp_list,
self._INCREASE)
if rollback:
with zvmutils.ignore_errors():
self._add_zfcp_to_pool(fcp, wwpn, lun, size)
with zvmutils.ignore_errors():
self._add_zfcp(instance, fcp, wwpn, lun, size)
if mountpoint:
with zvmutils.ignore_errors():
self._create_mountpoint(instance, fcp, wwpn, lun,
mountpoint)
def attach_volume_inactive(self, context, connection_info, instance,
mountpoint, rollback=True):
"""Attach a volume to an shutdown instance."""
(lun, wwpn, size, fcp) = self._extract_connection_info(context,
connection_info)
fcp_list = fcp.split(';')
do_attach = not self._is_fcp_in_use(instance)
self._update_instance_fcp_map_if_unlocked(instance['name'],
fcp_list, self._INCREASE)
os_version = instance.system_metadata['image_os_version']
try:
self._add_zfcp_to_pool(fcp, wwpn, lun, size)
self._allocate_zfcp(instance, fcp, size, wwpn, lun)
self._notice_attach(instance, fcp, wwpn, lun, mountpoint,
os_version)
if do_attach:
for dev_no in fcp_list:
self._attach_device(instance['name'], dev_no)
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError,
exception.ZVMVolumeError):
with excutils.save_and_reraise_exception():
self._update_instance_fcp_map_if_unlocked(instance['name'],
fcp_list,
self._DECREASE)
if rollback:
with zvmutils.ignore_errors():
self._notice_detach(instance, fcp, wwpn, lun,
mountpoint, os_version)
with zvmutils.ignore_errors():
self._remove_zfcp_from_pool(wwpn, lun)
with zvmutils.ignore_errors():
if do_attach:
for dev_no in fcp_list:
self._detach_device(instance['name'], dev_no)
self._update_instance_fcp_map_if_unlocked(
instance['name'], fcp_list, self._REMOVE)
def detach_volume_inactive(self, connection_info, instance, mountpoint,
rollback=True):
"""Detach a volume from an shutdown instance."""
(lun, wwpn, size, fcp) = self._extract_connection_info(None,
connection_info)
fcp_list = fcp.split(';')
self._update_instance_fcp_map_if_unlocked(instance['name'],
fcp_list, self._DECREASE)
do_detach = not self._is_fcp_in_use(instance)
os_version = instance.system_metadata['image_os_version']
try:
self._remove_zfcp(instance, fcp, wwpn, lun)
self._remove_zfcp_from_pool(wwpn, lun)
self._notice_detach(instance, fcp, wwpn, lun, mountpoint,
os_version)
if do_detach:
for dev_no in fcp_list:
self._detach_device(instance['name'], dev_no)
self._update_instance_fcp_map_if_unlocked(instance['name'],
fcp_list,
self._REMOVE)
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError,
exception.ZVMVolumeError):
with excutils.save_and_reraise_exception():
self._update_instance_fcp_map_if_unlocked(instance['name'],
fcp_list,
self._INCREASE)
if rollback:
with zvmutils.ignore_errors():
self._attach_device(instance['name'], fcp)
with zvmutils.ignore_errors():
self._notice_attach(instance, fcp, wwpn, lun,
mountpoint, os_version)
with zvmutils.ignore_errors():
self._add_zfcp_to_pool(fcp, wwpn, lun, size)
with zvmutils.ignore_errors():
self._allocate_zfcp(instance, fcp, size, wwpn, lun)
def volume_boot_init(self, instance, fcp):
self._update_instance_fcp_map_if_unlocked(instance['name'], [fcp],
self._INCREASE)
self._attach_device(instance['name'], fcp)
def volume_boot_cleanup(self, instance, fcp):
self._update_instance_fcp_map_if_unlocked(instance['name'], [fcp],
self._FORCE_REMOVE)
self._detach_device(instance['name'], fcp)
def _expand_fcp_list(self, fcp_list):
"""Expand fcp list string into a python list object which contains
each fcp devices in the list string. A fcp list is composed of fcp
device addresses, range indicator '-', and split indicator ';'.
For example, if fcp_list is
"0011-0013;0015;0017-0018", expand_fcp_list(fcp_list) will return
[0011, 0012, 0013, 0015, 0017, 0018].
"""
LOG.debug("Expand FCP list %s", fcp_list)
if not fcp_list:
return set()
range_pattern = '[0-9a-fA-F]{1,4}(-[0-9a-fA-F]{1,4})?'
match_pattern = "^(%(range)s)(;%(range)s)*$" % {'range': range_pattern}
if not re.match(match_pattern, fcp_list):
errmsg = _("Invalid FCP address %s") % fcp_list
raise exception.ZVMDriverError(msg=errmsg)
fcp_devices = set()
for _range in fcp_list.split(';'):
if '-' not in _range:
# single device
fcp_addr = int(_range, 16)
fcp_devices.add("%04x" % fcp_addr)
else:
# a range of address
(_min, _max) = _range.split('-')
_min = int(_min, 16)
_max = int(_max, 16)
for fcp_addr in range(_min, _max + 1):
fcp_devices.add("%04x" % fcp_addr)
# remove duplicate entries
return fcp_devices
def _attach_device(self, node, addr, mode='0'):
"""Attach a device to a node."""
body = [' '.join(['--dedicatedevice', addr, addr, mode])]
self._xcat_chvm(node, body)
def _detach_device(self, node, vdev):
"""Detach a device from a node."""
body = [' '.join(['--undedicatedevice', vdev])]
self._xcat_chvm(node, body)
def _online_device(self, node, dev):
"""After attaching a device to a node, the device should be made
online before it being in use.
"""
body = ["command=cio_ignore -r %s" % dev]
self._xcat_xdsh(node, body)
body = ["command=chccwdev -e %s" % dev]
self._xcat_xdsh(node, body)
def _is_fcp_in_use(self, instance):
if instance['name'] in self._instance_fcp_map:
count = self._instance_fcp_map.get(instance['name'])['count']
if count > 0:
return True
return False
def _notice_attach(self, instance, fcp, wwpn, lun, mountpoint, os_version):
# Create and send volume file
action = self._actions['attach_volume']
parms = self._get_volume_parms(action, fcp, wwpn, lun)
self._send_notice(instance, parms)
# Create and send mount point file
action = self._actions['create_mountpoint']
parms = self._get_mountpoint_parms(action, fcp, wwpn, lun, mountpoint,
os_version)
self._send_notice(instance, parms)
def _notice_detach(self, instance, fcp, wwpn, lun, mountpoint, os_version):
# Create and send volume file
action = self._actions['detach_volume']
parms = self._get_volume_parms(action, fcp, wwpn, lun)
self._send_notice(instance, parms)
# Create and send mount point file
action = self._actions['remove_mountpoint']
parms = self._get_mountpoint_parms(action, fcp, wwpn, lun, mountpoint,
os_version)
self._send_notice(instance, parms)
def _get_volume_parms(self, action, fcp, wwpn, lun):
action = "action=%s" % action
# Replace the ';' in wwpn/fcp to ',' in script file since shell will
# treat ';' as a new line
fcp = fcp.replace(';', ',')
fcp = "fcpAddr=%s" % fcp
wwpn = wwpn.replace(';', ',')
wwpn = "wwpn=%s" % wwpn
lun = "lun=%s" % lun
parmline = ' '.join([action, fcp, wwpn, lun])
return parmline
def _get_mountpoint_parms(self, action, fcp, wwpn, lun,
mountpoint, os_version):
action_parm = "action=%s" % action
mountpoint = "tgtFile=%s" % mountpoint
# Replace the ';' in wwpn/fcp to ',' in script file since shell will
# treat ';' as a new line
wwpn = wwpn.replace(';', ',')
fcp = fcp.replace(';', ',')
if action == self._actions['create_mountpoint']:
dist = self._dist_manager.get_linux_dist(os_version)()
srcdev = dist.assemble_zfcp_srcdev(fcp, wwpn, lun)
srcfile = "srcFile=%s" % srcdev
parmline = ' '.join([action_parm, mountpoint, srcfile])
else:
parmline = ' '.join([action_parm, mountpoint])
return parmline
def _send_notice(self, instance, parms):
zvmutils.aemod_handler(instance['name'], const.DISK_FUNC_NAME, parms)
def _add_zfcp_to_pool(self, fcp, wwpn, lun, size):
body = [' '.join(['--addzfcp2pool', self._pool_name, 'free', wwpn,
lun, size, fcp])]
self._xcat_chhy(body)
def _remove_zfcp_from_pool(self, wwpn, lun):
body = [' '.join(['--removezfcpfrompool', CONF.zvm_scsi_pool, lun,
wwpn])]
self._xcat_chhy(body)
def _add_zfcp(self, instance, fcp, wwpn, lun, size):
body = [' '.join(['--addzfcp', CONF.zvm_scsi_pool, fcp, str(0), size,
str(0), wwpn, lun])]
self._xcat_chvm(instance['name'], body)
def _remove_zfcp(self, instance, fcp, wwpn, lun):
body = [' '.join(['--removezfcp', fcp, wwpn, lun, '1'])]
self._xcat_chvm(instance['name'], body)
def _create_mountpoint(self, instance, fcp, wwpn, lun, mountpoint):
os_version = instance.system_metadata['image_os_version']
dist = self._dist_manager.get_linux_dist(os_version)()
srcdev = dist.assemble_zfcp_srcdev(fcp, wwpn, lun)
body = [" ".join(['--createfilesysnode', srcdev, mountpoint])]
self._xcat_chvm(instance['name'], body)
def _remove_mountpoint(self, instance, mountpoint):
body = [' '.join(['--removefilesysnode', mountpoint])]
self._xcat_chvm(instance['name'], body)
def _allocate_zfcp(self, instance, fcp, size, wwpn, lun):
body = [" ".join(['--reservezfcp', CONF.zvm_scsi_pool, 'used',
instance['name'], fcp, size, wwpn, lun])]
self._xcat_chhy(body)
def _xcat_chvm(self, node, body):
url = self._xcat_url.chvm('/' + node)
zvmutils.xcat_request('PUT', url, body)
def _xcat_chhy(self, body):
url = self._xcat_url.chhv('/' + self._host)
zvmutils.xcat_request('PUT', url, body)
def _xcat_xdsh(self, node, body):
url = self._xcat_url.xdsh('/' + node)
zvmutils.xcat_request('PUT', url, body)
def _xcat_rinv(self, fields):
url = self._xcat_url.rinv('/' + self._host, fields)
return zvmutils.xcat_request('GET', url)