Huang Rui 2e3d781d86 Move nova zvm virt driver to separate namespace
Currently, nova zvm virt driver is in nova namespace. This result
in some troubles when generating config file, generating docs and
packaging.

All changes in this commit are trying to move nova zvm virt driver
code into new namespace - nova_zvm .

Change-Id: I5251069dfd24ff4e337e9308439415482eb2234c
2017-03-23 12:51:13 +08:00

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)