Including all python modules for nova-zvm-virt-driver and neutron-zvm-plugin. Change-Id: I72dd9f64fc412cbf10f5e7ab6e4ac465a977e849
		
			
				
	
	
		
			775 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			775 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
# 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 os
 | 
						|
from random import randint
 | 
						|
import re
 | 
						|
import time
 | 
						|
 | 
						|
from oslo.config import cfg
 | 
						|
 | 
						|
import nova.context
 | 
						|
from nova.objects import block_device as block_device_obj
 | 
						|
from nova.objects import instance as instance_obj
 | 
						|
from nova.openstack.common.gettextutils import _
 | 
						|
from nova.openstack.common import jsonutils
 | 
						|
from nova.openstack.common import log as logging
 | 
						|
from nova.virt.zvm import exception
 | 
						|
from nova.virt.zvm import utils as zvmutils
 | 
						|
from nova import volume
 | 
						|
 | 
						|
 | 
						|
LOG = logging.getLogger(__name__)
 | 
						|
CONF = cfg.CONF
 | 
						|
 | 
						|
 | 
						|
class VolumeOperator(object):
 | 
						|
    """Volume operator on IBM z/VM platform."""
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self._svc_driver = SVCDriver()
 | 
						|
 | 
						|
    def init_host(self, host_stats):
 | 
						|
        try:
 | 
						|
            self._svc_driver.init_host(host_stats)
 | 
						|
        except (exception.ZVMDriverError, exception.ZVMVolumeError) as err:
 | 
						|
            LOG.warning(_("Initialize zhcp failed. Reason: %s") % err)
 | 
						|
 | 
						|
    def attach_volume_to_instance(self, context, connection_info, instance,
 | 
						|
                                  mountpoint, is_active, rollback=True):
 | 
						|
        """Attach a volume to an instance."""
 | 
						|
 | 
						|
        if None in [connection_info, instance, is_active]:
 | 
						|
            errmsg = _("Missing required parameters.")
 | 
						|
            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 None in [connection_info, instance, is_active]:
 | 
						|
            errmsg = _("Missing required parameters.")
 | 
						|
            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)
 | 
						|
 | 
						|
 | 
						|
@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."""
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self._xcat_url = zvmutils.XCATUrl()
 | 
						|
        self._path_utils = zvmutils.PathUtils()
 | 
						|
        self._host = CONF.zvm_host
 | 
						|
        self._pool_name = CONF.zvm_scsi_pool
 | 
						|
        self._fcp_pool = set()
 | 
						|
        self._instance_fcp_map = {}
 | 
						|
        self._is_instance_fcp_map_locked = False
 | 
						|
        self._volume_api = volume.API()
 | 
						|
 | 
						|
        self._actions = {'attach_volume': 'addScsiVolume',
 | 
						|
                        'detach_volume': 'removeScsiVolume',
 | 
						|
                        'create_mountpoint': 'createfilesysnode',
 | 
						|
                        'remove_mountpoint': 'removefilesysnode'}
 | 
						|
 | 
						|
        self._RESERVE = 0
 | 
						|
        self._INCREASE = 1
 | 
						|
        self._DECREASE = 2
 | 
						|
        self._REMOVE = 3
 | 
						|
 | 
						|
    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.error(errmsg)
 | 
						|
            raise exception.ZVMVolumeError(msg=errmsg)
 | 
						|
        self._init_fcp_pool(fcp_list)
 | 
						|
 | 
						|
    def _init_fcp_pool(self, fcp_list):
 | 
						|
        """Map all instances and their fcp devices, and record all free fcps.
 | 
						|
        One instance should use only one fcp device so far.
 | 
						|
        """
 | 
						|
 | 
						|
        self._fcp_pool = self._expand_fcp_list(fcp_list)
 | 
						|
        self._instance_fcp_map = {}
 | 
						|
        # Any 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:
 | 
						|
                    _fcp = connection_info['data']['zvm_fcp']
 | 
						|
                    if _fcp and _fcp in self._fcp_pool:
 | 
						|
                        self._update_instance_fcp_map(instance_name, _fcp,
 | 
						|
                                                      self._INCREASE)
 | 
						|
                    if _fcp and _fcp not in self._fcp_pool:
 | 
						|
                        errmsg = _("FCP device %s is not configured but " +
 | 
						|
                                   "is used by %s.") % (_fcp, instance_name)
 | 
						|
                        LOG.warning(errmsg)
 | 
						|
                except (TypeError, KeyError):
 | 
						|
                    pass
 | 
						|
 | 
						|
        for _key in self._instance_fcp_map.keys():
 | 
						|
            fcp = self._instance_fcp_map.get(_key)['fcp']
 | 
						|
            self._fcp_pool.remove(fcp)
 | 
						|
        self._is_instance_fcp_map_locked = False
 | 
						|
 | 
						|
    def _update_instance_fcp_map_if_unlocked(self, instance_name, fcp, action):
 | 
						|
        while self._is_instance_fcp_map_locked:
 | 
						|
            time.sleep(1)
 | 
						|
        self._update_instance_fcp_map(instance_name, fcp, action)
 | 
						|
 | 
						|
    def _update_instance_fcp_map(self, instance_name, fcp, action):
 | 
						|
        fcp = fcp.lower()
 | 
						|
        if instance_name in self._instance_fcp_map:
 | 
						|
            # One instance should use only one fcp device so far
 | 
						|
            current_fcp = self._instance_fcp_map.get(instance_name)['fcp']
 | 
						|
            if fcp != current_fcp:
 | 
						|
                errmsg = _("Instance %(ins_name)s has multiple FCP devices " +
 | 
						|
                           "attached! FCP1: %(fcp1)s, FCP2: %(fcp2)s"
 | 
						|
                           ) % {'ins_name': instance_name, 'fcp1': fcp,
 | 
						|
                                'fcp2': current_fcp}
 | 
						|
                LOG.warning(errmsg)
 | 
						|
                return
 | 
						|
 | 
						|
        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 a fcp device which already " +
 | 
						|
                               "has volumes attached on: %(ins_name)s:%(fcp)s"
 | 
						|
                               ) % {'ins_name': instance_name, 'fcp': fcp}
 | 
						|
                    LOG.warning(errmsg)
 | 
						|
            else:
 | 
						|
                new_item = {instance_name: {'fcp': fcp, 'count': 0}}
 | 
						|
                self._instance_fcp_map.update(new_item)
 | 
						|
 | 
						|
        elif action == self._INCREASE:
 | 
						|
            if instance_name in self._instance_fcp_map:
 | 
						|
                count = self._instance_fcp_map[instance_name]['count']
 | 
						|
                new_item = {instance_name: {'fcp': fcp, 'count': count + 1}}
 | 
						|
                self._instance_fcp_map.update(new_item)
 | 
						|
            else:
 | 
						|
                new_item = {instance_name: {'fcp': fcp, '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': fcp,
 | 
						|
                                                'count': count - 1}}
 | 
						|
                    self._instance_fcp_map.update(new_item)
 | 
						|
                else:
 | 
						|
                    fcp = self._instance_fcp_map[instance_name]['fcp']
 | 
						|
                    self._instance_fcp_map.pop(instance_name)
 | 
						|
                    self._fcp_pool.add(fcp)
 | 
						|
            else:
 | 
						|
                errmsg = _("Try to decrease an inexistent map item: " +
 | 
						|
                           "%(ins_name)s:%(fcp)s"
 | 
						|
                           ) % {'ins_name': instance_name, 'fcp': fcp}
 | 
						|
                LOG.warning(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 while some volumes " +
 | 
						|
                               "are still attached on: %(ins_name)s:%(fcp)s"
 | 
						|
                               ) % {'ins_name': instance_name, 'fcp': fcp}
 | 
						|
                    LOG.warning(errmsg)
 | 
						|
                else:
 | 
						|
                    fcp = self._instance_fcp_map[instance_name]['fcp']
 | 
						|
                    self._instance_fcp_map.pop(instance_name)
 | 
						|
                    self._fcp_pool.add(fcp)
 | 
						|
            else:
 | 
						|
                errmsg = _("Try to remove an inexistent map item: " +
 | 
						|
                           "%(ins_name)s:%(fcp)s"
 | 
						|
                           ) % {'ins_name': instance_name, 'fcp': fcp}
 | 
						|
                LOG.warning(errmsg)
 | 
						|
 | 
						|
        else:
 | 
						|
            errmsg = _("Unrecognized option: %s") % action
 | 
						|
            LOG.warning(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'])
 | 
						|
            return connection_info
 | 
						|
        except (TypeError, KeyError, ValueError):
 | 
						|
            return None
 | 
						|
 | 
						|
    def get_volume_connector(self, instance):
 | 
						|
        try:
 | 
						|
            fcp = self._instance_fcp_map.get(instance['name'])['fcp']
 | 
						|
        except Exception:
 | 
						|
            fcp = None
 | 
						|
        if not fcp:
 | 
						|
            fcp = self._get_fcp_from_pool()
 | 
						|
            if fcp:
 | 
						|
                self._update_instance_fcp_map_if_unlocked(instance['name'],
 | 
						|
                                                          fcp, self._RESERVE)
 | 
						|
 | 
						|
        if not fcp:
 | 
						|
            errmsg = _("No available FCP device found.")
 | 
						|
            LOG.error(errmsg)
 | 
						|
            raise exception.ZVMVolumeError(msg=errmsg)
 | 
						|
        fcp = fcp.lower()
 | 
						|
 | 
						|
        wwpn = self._get_wwpn(fcp)
 | 
						|
        if not wwpn:
 | 
						|
            errmsg = _("FCP device %s has no available WWPN.") % fcp
 | 
						|
            LOG.error(errmsg)
 | 
						|
            raise exception.ZVMVolumeError(msg=errmsg)
 | 
						|
        wwpn = wwpn.lower()
 | 
						|
 | 
						|
        return {'zvm_fcp': fcp, 'wwpns': [wwpn], 'host': CONF.zvm_host}
 | 
						|
 | 
						|
    def _get_wwpn(self, fcp):
 | 
						|
        states = ['active', 'free']
 | 
						|
        for _state in states:
 | 
						|
            fcps_info = self._list_fcp_details(_state)
 | 
						|
            if not fcps_info:
 | 
						|
                continue
 | 
						|
            wwpn = self._extract_wwpn_from_fcp_info(fcp, fcps_info)
 | 
						|
            if wwpn:
 | 
						|
                return wwpn
 | 
						|
 | 
						|
    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 _extract_wwpn_from_fcp_info(self, fcp, fcps_info):
 | 
						|
        """The FCP infomation would look 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
 | 
						|
 | 
						|
        """
 | 
						|
 | 
						|
        lines_per_item = 5
 | 
						|
        num_fcps = len(fcps_info) / lines_per_item
 | 
						|
        fcp = fcp.upper()
 | 
						|
        for _cur in range(0, num_fcps):
 | 
						|
            # Find target FCP device
 | 
						|
            if fcp not in fcps_info[_cur * lines_per_item]:
 | 
						|
                continue
 | 
						|
            # Try to get NPIV WWPN first
 | 
						|
            wwpn_info = fcps_info[(_cur + 1) * lines_per_item - 3]
 | 
						|
            wwpn = self._get_wwpn_from_line(wwpn_info)
 | 
						|
            if not wwpn:
 | 
						|
                # Get physical WWPN if NPIV WWPN is none
 | 
						|
                wwpn_info = fcps_info[(_cur + 1) * lines_per_item - 1]
 | 
						|
                wwpn = self._get_wwpn_from_line(wwpn_info)
 | 
						|
            return wwpn
 | 
						|
 | 
						|
    def _get_wwpn_from_line(self, info_line):
 | 
						|
        wwpn = info_line.split(':')[-1].strip()
 | 
						|
        if wwpn and wwpn.upper() != 'NONE':
 | 
						|
            return wwpn
 | 
						|
        else:
 | 
						|
            return None
 | 
						|
 | 
						|
    def _get_fcp_from_pool(self):
 | 
						|
        if self._fcp_pool:
 | 
						|
            return self._fcp_pool.pop()
 | 
						|
 | 
						|
        self._init_fcp_pool(CONF.zvm_fcp_list)
 | 
						|
        if self._fcp_pool:
 | 
						|
            return self._fcp_pool.pop()
 | 
						|
 | 
						|
    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 = connection_info['data']['zvm_fcp']
 | 
						|
 | 
						|
            return (lun.lower(), wwpn.lower(), size, fcp.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)
 | 
						|
        try:
 | 
						|
            self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
 | 
						|
                                                      self._INCREASE)
 | 
						|
            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):
 | 
						|
            self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
 | 
						|
                                                      self._DECREASE)
 | 
						|
            do_detach = not self._is_fcp_in_use(instance, fcp)
 | 
						|
            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:
 | 
						|
                        self._detach_device(instance['name'], fcp)
 | 
						|
            raise
 | 
						|
 | 
						|
    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)
 | 
						|
        try:
 | 
						|
            self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
 | 
						|
                                                      self._DECREASE)
 | 
						|
            do_detach = not self._is_fcp_in_use(instance, fcp)
 | 
						|
            if mountpoint:
 | 
						|
                self._remove_mountpoint(instance, mountpoint)
 | 
						|
            self._remove_zfcp(instance, fcp, wwpn, lun)
 | 
						|
            self._remove_zfcp_from_pool(wwpn, lun)
 | 
						|
            if do_detach:
 | 
						|
                self._detach_device(instance['name'], fcp)
 | 
						|
                self._update_instance_fcp_map_if_unlocked(instance['name'],
 | 
						|
                                                          fcp, self._REMOVE)
 | 
						|
        except (exception.ZVMXCATRequestFailed,
 | 
						|
                exception.ZVMInvalidXCATResponseDataError,
 | 
						|
                exception.ZVMXCATInternalError,
 | 
						|
                exception.ZVMVolumeError):
 | 
						|
            self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
 | 
						|
                                                      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)
 | 
						|
            raise
 | 
						|
 | 
						|
    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)
 | 
						|
        try:
 | 
						|
            do_attach = not self._is_fcp_in_use(instance, fcp)
 | 
						|
            self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
 | 
						|
                                                      self._INCREASE)
 | 
						|
            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)
 | 
						|
            if do_attach:
 | 
						|
                self._attach_device(instance['name'], fcp)
 | 
						|
        except (exception.ZVMXCATRequestFailed,
 | 
						|
                exception.ZVMInvalidXCATResponseDataError,
 | 
						|
                exception.ZVMXCATInternalError,
 | 
						|
                exception.ZVMVolumeError):
 | 
						|
            self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
 | 
						|
                                                      self._DECREASE)
 | 
						|
            do_detach = not self._is_fcp_in_use(instance, fcp)
 | 
						|
            if rollback:
 | 
						|
                with zvmutils.ignore_errors():
 | 
						|
                    self._notice_detach(instance, fcp, wwpn, lun, mountpoint)
 | 
						|
                with zvmutils.ignore_errors():
 | 
						|
                    self._remove_zfcp_from_pool(wwpn, lun)
 | 
						|
                with zvmutils.ignore_errors():
 | 
						|
                    if do_detach:
 | 
						|
                        self._detach_device(instance['name'], fcp)
 | 
						|
                        self._update_instance_fcp_map_if_unlocked(
 | 
						|
                                instance['name'], fcp, self._REMOVE)
 | 
						|
            raise
 | 
						|
 | 
						|
    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)
 | 
						|
        try:
 | 
						|
            self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
 | 
						|
                                                      self._DECREASE)
 | 
						|
            do_detach = not self._is_fcp_in_use(instance, fcp)
 | 
						|
            self._remove_zfcp(instance, fcp, wwpn, lun)
 | 
						|
            self._remove_zfcp_from_pool(wwpn, lun)
 | 
						|
            self._notice_detach(instance, fcp, wwpn, lun, mountpoint)
 | 
						|
            if do_detach:
 | 
						|
                self._detach_device(instance['name'], fcp)
 | 
						|
                self._update_instance_fcp_map_if_unlocked(instance['name'],
 | 
						|
                                                          fcp, self._REMOVE)
 | 
						|
        except (exception.ZVMXCATRequestFailed,
 | 
						|
                exception.ZVMInvalidXCATResponseDataError,
 | 
						|
                exception.ZVMXCATInternalError,
 | 
						|
                exception.ZVMVolumeError):
 | 
						|
            self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
 | 
						|
                                                      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)
 | 
						|
                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)
 | 
						|
            raise
 | 
						|
 | 
						|
    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, fcp):
 | 
						|
        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):
 | 
						|
        file_path = self._get_file_for_punch(instance)
 | 
						|
 | 
						|
        # Create and send volume file
 | 
						|
        action = self._actions['attach_volume']
 | 
						|
        lines = self._get_volume_lines(action, fcp, wwpn, lun)
 | 
						|
        self._send_notice(instance, file_path, lines)
 | 
						|
 | 
						|
        # Create and send mount point file
 | 
						|
        action = self._actions['create_mountpoint']
 | 
						|
        lines = self._get_mountpoint_lines(action, fcp, wwpn, lun, mountpoint)
 | 
						|
        self._send_notice(instance, file_path, lines)
 | 
						|
 | 
						|
    def _notice_detach(self, instance, fcp, wwpn, lun, mountpoint):
 | 
						|
        file_path = self._get_file_for_punch(instance)
 | 
						|
 | 
						|
        # Create and send volume file
 | 
						|
        action = self._actions['detach_volume']
 | 
						|
        lines = self._get_volume_lines(action, fcp, wwpn, lun)
 | 
						|
        self._send_notice(instance, file_path, lines)
 | 
						|
 | 
						|
        # Create and send mount point file
 | 
						|
        action = self._actions['remove_mountpoint']
 | 
						|
        lines = self._get_mountpoint_lines(action, fcp, wwpn, lun, mountpoint)
 | 
						|
        self._send_notice(instance, file_path, lines)
 | 
						|
 | 
						|
    def _get_volume_lines(self, action, fcp, wwpn, lun):
 | 
						|
        comments = '# xCAT Init\n'
 | 
						|
        action_line = "action=%s\n" % action
 | 
						|
        fcp_line = "fcpAddr=%s\n" % fcp
 | 
						|
        wwpn_line = "wwpn=%s\n" % wwpn
 | 
						|
        lun_line = "lun=%s\n" % lun
 | 
						|
        return [comments, action_line, fcp_line, wwpn_line, lun_line]
 | 
						|
 | 
						|
    def _get_mountpoint_lines(self, action, fcp, wwpn, lun, mountpoint):
 | 
						|
        comments = '# xCAT Init\n'
 | 
						|
        action_line = "action=%s\n" % action
 | 
						|
        mountpoint_line = "tgtFile=%s\n" % mountpoint
 | 
						|
        if action == self._actions['create_mountpoint']:
 | 
						|
            path = self._get_zfcp_path_pattern()
 | 
						|
            srcdev = path % {'fcp': fcp, 'wwpn': wwpn, 'lun': lun}
 | 
						|
            srcdev_line = "srcFile=%s\n" % srcdev
 | 
						|
            return [comments, action_line, mountpoint_line, srcdev_line]
 | 
						|
        else:
 | 
						|
            return [comments, action_line, mountpoint_line]
 | 
						|
 | 
						|
    def _get_file_for_punch(self, instance):
 | 
						|
        dir_path = self._path_utils.get_punch_time_path()
 | 
						|
        file_name = "%08x.disk" % randint(0, int('FFFFFFFF', 16))
 | 
						|
        file_path = os.path.join(dir_path, file_name)
 | 
						|
        return file_path
 | 
						|
 | 
						|
    def _send_notice(self, instance, file_path, lines):
 | 
						|
        with wrap_internal_errors():
 | 
						|
            with open(file_path, 'w') as f:
 | 
						|
                f.writelines(lines)
 | 
						|
 | 
						|
        # zvmutils.punch_file will remove the file after punching
 | 
						|
        zvmutils.punch_file(instance['name'], file_path, 'X')
 | 
						|
 | 
						|
    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):
 | 
						|
        path = self._get_zfcp_path_pattern()
 | 
						|
        srcdev = path % {'fcp': fcp, 'wwpn': wwpn, 'lun': 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)
 | 
						|
 | 
						|
    def _get_zfcp_path_pattern(self):
 | 
						|
        return '/dev/disk/by-path/ccw-0.0.%(fcp)s-zfcp-0x%(wwpn)s:0x%(lun)s'
 |