1536 lines
60 KiB
Python
1536 lines
60 KiB
Python
# Copyright (c) 2019 MacroSAN Technologies Co., Ltd.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
"""Volume Drivers for MacroSAN SAN."""
|
|
|
|
from contextlib import contextmanager
|
|
import math
|
|
import re
|
|
import socket
|
|
import time
|
|
import uuid
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
from oslo_utils import strutils
|
|
from oslo_utils import timeutils
|
|
|
|
from cinder import context
|
|
from cinder.coordination import synchronized
|
|
from cinder import exception
|
|
from cinder.i18n import _
|
|
from cinder import interface
|
|
from cinder import utils
|
|
from cinder.volume import configuration
|
|
from cinder.volume import driver
|
|
from cinder.volume.drivers.macrosan import config
|
|
from cinder.volume.drivers.macrosan import devop_client
|
|
from cinder.volume.drivers.san import san
|
|
from cinder.volume import qos_specs
|
|
from cinder.volume import volume_types
|
|
from cinder.volume import volume_utils
|
|
from cinder.zonemanager import utils as fczm_utils
|
|
|
|
version = '1.0.1'
|
|
lock_name = 'MacroSAN'
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(config.macrosan_opts, group=configuration.SHARED_CONF_GROUP)
|
|
|
|
|
|
@contextmanager
|
|
def ignored(*exceptions):
|
|
try:
|
|
yield
|
|
except exceptions:
|
|
pass
|
|
|
|
|
|
def record_request_id(fn):
|
|
def _record_request_id(*vargs, **kv):
|
|
ctx = context.context.get_current()
|
|
devop_client.context_request_id = ctx.request_id
|
|
|
|
return fn(*vargs, **kv)
|
|
return _record_request_id
|
|
|
|
|
|
def replication_synced(params):
|
|
return (params['replication_enabled'] and
|
|
params['replication_mode'] == 'sync')
|
|
|
|
|
|
class MacroSANBaseDriver(driver.VolumeDriver):
|
|
"""Base driver for MacroSAN SAN."""
|
|
|
|
CI_WIKI_NAME = 'MacroSAN Volume CI'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Initialize the driver."""
|
|
super(MacroSANBaseDriver, self).__init__(*args, **kwargs)
|
|
self.configuration.append_config_values(config.macrosan_opts)
|
|
self.configuration.append_config_values(san.san_opts)
|
|
self._stats = {}
|
|
self.use_multipath = True
|
|
self.owners = ['SP1', 'SP2']
|
|
self.owner_idx = 0
|
|
self.volume_backend_name = (
|
|
self.configuration.safe_get('volume_backend_name') or 'MacroSAN')
|
|
|
|
self.username = self.configuration.san_login
|
|
self.passwd = self.configuration.san_password
|
|
self.sp1_ipaddr, self.sp2_ipaddr = (
|
|
self.configuration.san_ip.replace(' ', '').split(","))
|
|
self.login_info = self.username + self.passwd
|
|
|
|
if self.configuration.macrosan_sdas_ipaddrs:
|
|
self.sdas_username = self.configuration.macrosan_sdas_username
|
|
self.sdas_passwd = self.configuration.macrosan_sdas_password
|
|
self.sdas_sp1_ipaddr, self.sdas_sp2_ipaddr = (
|
|
self.configuration.macrosan_sdas_ipaddrs)
|
|
self.sdas_sp1_ipaddr = (
|
|
self.sdas_sp1_ipaddr.replace('/', ',').replace(' ', ''))
|
|
self.sdas_sp2_ipaddr = (
|
|
self.sdas_sp2_ipaddr.replace('/', ',').replace(' ', ''))
|
|
self.sdas_login_info = self.sdas_username + self.sdas_passwd
|
|
|
|
if self.configuration.macrosan_replication_ipaddrs:
|
|
self.rep_username = (
|
|
self.configuration.macrosan_replication_username)
|
|
|
|
self.rep_passwd = self.configuration.macrosan_replication_password
|
|
self.rep_sp1_ipaddr, self.rep_sp2_ipaddr = (
|
|
self.configuration.macrosan_replication_ipaddrs)
|
|
self.rep_sp1_ipaddr = (
|
|
self.rep_sp1_ipaddr.replace('/', ',').replace(' ', ''))
|
|
self.rep_sp2_ipaddr = (
|
|
self.rep_sp2_ipaddr.replace('/', ',').replace(' ', ''))
|
|
self.replica_login_info = self.rep_username + self.rep_passwd
|
|
self.replication_params = {
|
|
'destination':
|
|
{'sp1': self.configuration.
|
|
macrosan_replication_destination_ports[0],
|
|
'sp2': self.configuration.
|
|
macrosan_replication_destination_ports[1]}}
|
|
|
|
self.client = None
|
|
self.replica_client = None
|
|
self.sdas_client = None
|
|
self.storage_protocol = None
|
|
self.device_uuid = None
|
|
self.client_info = dict()
|
|
|
|
self.lun_params = {}
|
|
self.lun_mode = self.configuration.san_thin_provision
|
|
if self.lun_mode:
|
|
self.lun_params = (
|
|
{'extent-size':
|
|
self.configuration.macrosan_thin_lun_extent_size,
|
|
'low-watermark':
|
|
self.configuration.macrosan_thin_lun_low_watermark,
|
|
'high-watermark':
|
|
self.configuration.macrosan_thin_lun_high_watermark})
|
|
self.pool = self.configuration.macrosan_pool
|
|
|
|
self.force_unmap_itl_when_deleting = (
|
|
self.configuration.macrosan_force_unmap_itl)
|
|
self.snapshot_resource_ratio = (
|
|
self.configuration.macrosan_snapshot_resource_ratio)
|
|
global timing_on
|
|
timing_on = self.configuration.macrosan_log_timing
|
|
|
|
self.initialize_iscsi_info()
|
|
|
|
def _size_str_to_int(self, size_in_g):
|
|
if int(size_in_g) == 0:
|
|
return 1
|
|
return int(size_in_g)
|
|
|
|
def _volume_name(self, volume):
|
|
try:
|
|
lun_uuid = re.search(r'macrosan uuid:(.+)',
|
|
volume['provider_location']).group(1)
|
|
return self.client.get_lun_name(lun_uuid)
|
|
except Exception:
|
|
return volume['id']
|
|
|
|
def _snapshot_name(self, snapshotid):
|
|
return snapshotid.replace('-', '')[:31]
|
|
|
|
def initialize_iscsi_info(self):
|
|
sp1_port, sp2_port = \
|
|
self.configuration.macrosan_client_default.split(';')
|
|
host = socket.gethostname()
|
|
self.client_info['default'] = {'client_name': host,
|
|
'sp1_port':
|
|
sp1_port.replace(' ', ''),
|
|
'sp2_port':
|
|
sp2_port.replace(' ', '')}
|
|
client_list = self.configuration.macrosan_client
|
|
if client_list:
|
|
for i in client_list:
|
|
client = i.strip('(').strip(')').split(";")
|
|
host, client_name, sp1_port, sp2_port = [j.strip() for j
|
|
in client]
|
|
self.client_info[host] = (
|
|
{'client_name': client_name,
|
|
'sp1_port': sp1_port.replace(' ', '').replace('/', ','),
|
|
'sp2_port': sp2_port.replace(' ', '').replace('/', ',')})
|
|
|
|
def _get_client_name(self, host):
|
|
if host in self.client_info:
|
|
return self.client_info[host]['client_name']
|
|
return self.client_info['default']['client_name']
|
|
|
|
@utils.synchronized('MacroSAN-Setup', external=True)
|
|
@record_request_id
|
|
def do_setup(self, context):
|
|
"""Any initialization the volume driver does while starting."""
|
|
LOG.debug('Enter in Macrosan do_setup.')
|
|
self.client = devop_client.Client(self.sp1_ipaddr,
|
|
self.sp2_ipaddr,
|
|
self.login_info)
|
|
|
|
if self.configuration.macrosan_sdas_ipaddrs:
|
|
self.sdas_client = (
|
|
devop_client.Client(self.sdas_sp1_ipaddr,
|
|
self.sdas_sp2_ipaddr,
|
|
self.sdas_login_info))
|
|
|
|
if self.configuration.macrosan_replication_ipaddrs:
|
|
self.replica_client = (
|
|
devop_client.Client(self.rep_sp1_ipaddr,
|
|
self.rep_sp2_ipaddr,
|
|
self.replica_login_info))
|
|
self.device_uuid = self.client.get_device_uuid()
|
|
self._do_setup()
|
|
LOG.debug('MacroSAN Cinder Driver setup complete.')
|
|
|
|
def _do_setup(self):
|
|
pass
|
|
|
|
def _get_owner(self):
|
|
owner = self.owners[self.owner_idx % 2]
|
|
self.owner_idx += 1
|
|
return owner
|
|
|
|
def check_for_setup_error(self):
|
|
"""Check any setup error."""
|
|
pass
|
|
|
|
def _check_volume_params(self, params):
|
|
if params['sdas'] and params['replication_enabled']:
|
|
raise exception.VolumeBackendAPIException(
|
|
data=_('sdas and replication can not be enabled at same time'))
|
|
|
|
if params['sdas'] and self.sdas_client is None:
|
|
raise exception.VolumeBackendAPIException(
|
|
data=_('sdas is not configured, cannot use sdas'))
|
|
|
|
if params['replication_enabled'] and self.replica_client is None:
|
|
raise exception.VolumeBackendAPIException(
|
|
data=_('replica is not configured, cannot use replication'))
|
|
|
|
def get_raid_list(self, size):
|
|
raids = self.client.get_raid_list(self.pool)
|
|
free = sum(raid['free_cap'] for raid in raids)
|
|
if size > free:
|
|
raise exception.VolumeBackendAPIException(_('Pool has not enough'
|
|
'free capacity'))
|
|
|
|
raids = sorted(raids, key=lambda x: x['free_cap'], reverse=True)
|
|
|
|
selected = []
|
|
cap = 0
|
|
for raid in raids:
|
|
if raid['free_cap']:
|
|
cap += raid['free_cap']
|
|
selected.append(raid['name'])
|
|
if cap >= size:
|
|
break
|
|
return selected
|
|
|
|
def _create_volume(self, name, size, params, owner=None, pool=None):
|
|
rmt_client = None
|
|
if params['sdas']:
|
|
rmt_client = self.sdas_client
|
|
elif params['replication_enabled']:
|
|
rmt_client = self.replica_client
|
|
|
|
owner = self._get_owner() if owner is None else owner
|
|
raids = []
|
|
pool = self.pool if pool is None else pool
|
|
|
|
if not params['lun_mode']:
|
|
raids = self.client.get_raid_list_to_create_lun(pool, size)
|
|
|
|
self.client.create_lun(name, owner, pool, raids,
|
|
params['lun_mode'], size, self.lun_params)
|
|
|
|
if params['qos-strategy']:
|
|
try:
|
|
self.client.enable_lun_qos(name, params['qos-strategy'])
|
|
except Exception:
|
|
self.client.delete_lun(name)
|
|
raise exception.VolumeBackendAPIException(
|
|
_('Enable lun qos failed.'))
|
|
|
|
if params['sdas'] or params['replication_enabled']:
|
|
res_size = int(max(int(size) * self.snapshot_resource_ratio, 1))
|
|
try:
|
|
raids = self.client.get_raid_list_to_create_lun(pool,
|
|
res_size)
|
|
self.client.setup_snapshot_resource(name, res_size, raids)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
self.client.delete_lun(name)
|
|
|
|
try:
|
|
raids = []
|
|
if not params['lun_mode']:
|
|
raids = rmt_client.get_raid_list_to_create_lun(
|
|
pool, size)
|
|
|
|
rmt_client.create_lun(name, owner, pool, raids,
|
|
params['lun_mode'], size,
|
|
self.lun_params)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
self.client.delete_snapshot_resource(name)
|
|
self.client.delete_lun(name)
|
|
|
|
try:
|
|
raids = rmt_client.get_raid_list_to_create_lun(pool,
|
|
res_size)
|
|
rmt_client.setup_snapshot_resource(name, res_size, raids)
|
|
except Exception:
|
|
with ignored(Exception):
|
|
rmt_client.delete_lun(name)
|
|
with excutils.save_and_reraise_exception():
|
|
self.client.delete_snapshot_resource(name)
|
|
self.client.delete_lun(name)
|
|
|
|
if params['sdas'] or replication_synced(params):
|
|
try:
|
|
self.client.create_dalun(name)
|
|
except Exception:
|
|
with ignored(Exception):
|
|
rmt_client.delete_snapshot_resource(name)
|
|
rmt_client.delete_lun(name)
|
|
with excutils.save_and_reraise_exception():
|
|
self.client.delete_snapshot_resource(name)
|
|
self.client.delete_lun(name)
|
|
elif params['replication_mode'] == 'async':
|
|
destination = self.replication_params['destination']
|
|
sp1_ipaddr = rmt_client.get_port_ipaddr(destination['sp1'])
|
|
sp2_ipaddr = rmt_client.get_port_ipaddr(destination['sp2'])
|
|
|
|
try:
|
|
self.client.enable_replication(name, sp1_ipaddr,
|
|
sp2_ipaddr)
|
|
self.client.startscan_replication(name)
|
|
except Exception:
|
|
with ignored(Exception):
|
|
rmt_client.delete_snapshot_resource(name)
|
|
rmt_client.delete_lun(name)
|
|
with excutils.save_and_reraise_exception():
|
|
self.client.delete_snapshot_resource(name)
|
|
self.client.delete_lun(name)
|
|
|
|
lun_uuid = self.client.get_lun_uuid(name)
|
|
return {'provider_location': 'macrosan uuid:%s' % lun_uuid}
|
|
|
|
def _parse_qos_strategy(self, volume_type):
|
|
qos_specs_id = volume_type.get('qos_specs_id')
|
|
|
|
if qos_specs_id is None:
|
|
return ''
|
|
|
|
ctx = context.get_admin_context()
|
|
specs = qos_specs.get_qos_specs(ctx, qos_specs_id)['specs']
|
|
|
|
return specs.pop('qos-strategy', '').strip() if specs else ''
|
|
|
|
def _default_volume_params(self):
|
|
params = {
|
|
'qos-strategy': '',
|
|
'replication_enabled': False,
|
|
'replication_mode': 'async',
|
|
'sdas': False,
|
|
'lun_mode': self.lun_mode
|
|
}
|
|
return params
|
|
|
|
def _parse_volume_params(self, volume):
|
|
params = self._default_volume_params()
|
|
|
|
if volume.volume_type_id is None:
|
|
return params
|
|
|
|
ctx = context.get_admin_context()
|
|
volume_type = volume_types.get_volume_type(ctx, volume.volume_type_id)
|
|
|
|
params['qos-strategy'] = self._parse_qos_strategy(volume_type)
|
|
|
|
specs = dict(volume_type).get('extra_specs')
|
|
for k, val in specs.items():
|
|
ks = k.lower().split(':')
|
|
if len(ks) == 2 and ks[0] != "capabilities":
|
|
continue
|
|
|
|
k = ks[-1]
|
|
if k not in params:
|
|
continue
|
|
|
|
else:
|
|
v = val.split()[-1]
|
|
val_type = type(params[k]).__name__
|
|
if val_type == 'int':
|
|
v = int(v)
|
|
elif val_type == 'bool':
|
|
v = strutils.bool_from_string(v)
|
|
params[k] = v
|
|
|
|
if params['sdas']:
|
|
params['lun_mode'] = False
|
|
|
|
return params
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def create_volume(self, volume):
|
|
"""Create a volume."""
|
|
name = volume['name']
|
|
size = self._size_str_to_int(volume['size'])
|
|
params = self._parse_volume_params(volume)
|
|
self._check_volume_params(params)
|
|
|
|
return self._create_volume(name, size, params)
|
|
|
|
def _delete_volume(self, name, params=None):
|
|
if not self.client.lun_exists(name):
|
|
return
|
|
|
|
if params is None:
|
|
params = self._default_volume_params()
|
|
|
|
if self.force_unmap_itl_when_deleting:
|
|
self.force_terminate_connection(name, False)
|
|
|
|
if params['sdas'] or replication_synced(params):
|
|
if self.client.dalun_exists(name):
|
|
self.client.suspend_dalun(name)
|
|
self.client.delete_dalun(name)
|
|
with ignored(Exception):
|
|
self.sdas_client.delete_snapshot_resource(name)
|
|
self.sdas_client.delete_lun(name)
|
|
self.client.delete_snapshot_resource(name)
|
|
|
|
if (params['replication_enabled'] and
|
|
params['replication_mode'] == 'async'):
|
|
if self.client.replication_enabled(name):
|
|
with ignored(Exception):
|
|
self.client.stopscan_replication(name)
|
|
self.client.pausereplicate(name)
|
|
self.client.disable_replication(name)
|
|
self.client.delete_snapshot_resource(name)
|
|
|
|
self.client.delete_lun(name)
|
|
|
|
try:
|
|
migrated_name = self.client.get_lun_name_from_rename_file(name)
|
|
if not migrated_name:
|
|
return
|
|
try:
|
|
self.client.rename_lun(migrated_name, name)
|
|
except Exception:
|
|
LOG.warning('========== failed to rename %(migrated_name)s'
|
|
' to %(name)s',
|
|
{'migrated_name': migrated_name, 'name': name})
|
|
except Exception:
|
|
return
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def delete_volume(self, volume):
|
|
"""Delete a volume."""
|
|
name = self._volume_name(volume)
|
|
params = self._parse_volume_params(volume)
|
|
self._delete_volume(name, params)
|
|
|
|
@utils.synchronized('MacroSAN-Attach-Detach', external=True)
|
|
def _attach_volume(self, context, volume, properties, remote=False):
|
|
return super(MacroSANBaseDriver, self)._attach_volume(context,
|
|
volume,
|
|
properties,
|
|
remote)
|
|
|
|
@utils.synchronized('MacroSAN-Attach-Detach', external=True)
|
|
def _detach_volume(self, context, attach_info, volume,
|
|
properties, force=False, remote=False,
|
|
ignore_errors=True):
|
|
return super(MacroSANBaseDriver, self)._detach_volume(context,
|
|
attach_info,
|
|
volume,
|
|
properties,
|
|
force,
|
|
remote,
|
|
ignore_errors)
|
|
|
|
def _create_snapshot(self, snapshot_name, volume_name, volume_size):
|
|
size = int(max(int(volume_size) * self.snapshot_resource_ratio, 1))
|
|
raids = self.client.get_raid_list_to_create_lun(self.pool, size)
|
|
if not self.client.snapshot_resource_exists(volume_name):
|
|
self.client.create_snapshot_resource(volume_name, raids, size)
|
|
try:
|
|
self.client.enable_snapshot_resource_autoexpand(volume_name)
|
|
except exception.VolumeBackendAPIException:
|
|
LOG.warning('========== Enable snapshot resource auto '
|
|
'expand for volume: %(volume_name)s error',
|
|
{'volume_name': volume_name})
|
|
|
|
if not self.client.snapshot_enabled(volume_name):
|
|
try:
|
|
self.client.enable_snapshot(volume_name)
|
|
except exception.VolumeBackendAPIException:
|
|
with excutils.save_and_reraise_exception():
|
|
self.client.delete_snapshot_resource(volume_name)
|
|
|
|
try:
|
|
self.client.create_snapshot_point(volume_name, snapshot_name)
|
|
pointid = self.client.get_snapshot_pointid(volume_name,
|
|
snapshot_name)
|
|
except exception.VolumeBackendAPIException:
|
|
with ignored(Exception):
|
|
self.client.disable_snapshot(volume_name)
|
|
self.client.delete_snapshot_resource(volume_name)
|
|
raise
|
|
|
|
return int(pointid)
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def create_snapshot(self, snapshot):
|
|
"""Create a snapshot."""
|
|
volume = snapshot['volume']
|
|
|
|
snapshot_name = self._snapshot_name(snapshot['name'])
|
|
volume_name = self._volume_name(volume)
|
|
|
|
pointid = self._create_snapshot(snapshot_name,
|
|
volume_name,
|
|
volume['size'])
|
|
return {'provider_location': 'pointid: %s' % pointid}
|
|
|
|
def _delete_snapshot(self, snapshot_name, volume_name, pointid):
|
|
if self.client.snapshot_point_exists(volume_name, pointid):
|
|
self.client.delete_snapshot_point(volume_name, pointid)
|
|
|
|
with ignored(Exception):
|
|
n = self.client.get_snapshot_point_num(volume_name)
|
|
if n != 0:
|
|
return
|
|
with ignored(Exception):
|
|
self.client.disable_snapshot(volume_name)
|
|
if not (self.client.dalun_exists(volume_name) or
|
|
self.client.replication_enabled(volume_name)):
|
|
self.client.delete_snapshot_resource(volume_name)
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def delete_snapshot(self, snapshot):
|
|
"""Delete a snapshot."""
|
|
volume = snapshot['volume']
|
|
provider = snapshot['provider_location']
|
|
if not provider:
|
|
return
|
|
|
|
m = re.findall(r'pointid: (\d+)', provider)
|
|
if m is None:
|
|
return
|
|
|
|
snapshot_name = self._snapshot_name(snapshot['id'])
|
|
volume_name = self._volume_name(volume)
|
|
self._delete_snapshot(snapshot_name, volume_name, m[0])
|
|
|
|
def _initialize_connection(self, name, host, wwns):
|
|
raise NotImplementedError
|
|
|
|
def _terminate_connection(self, name, host, wwns):
|
|
raise NotImplementedError
|
|
|
|
def _create_volume_from_snapshot(self, vol_name, vol_size,
|
|
vol_params, snp_name, pointid,
|
|
snp_vol_name, snp_vol_size):
|
|
self._create_volume(vol_name, vol_size, vol_params)
|
|
|
|
try:
|
|
self.client.create_snapshot_view(snp_name,
|
|
snp_vol_name,
|
|
pointid)
|
|
except Exception:
|
|
self._delete_volume(vol_name)
|
|
raise exception.VolumeBackendAPIException(
|
|
_('Create snapshot view failed.'))
|
|
try:
|
|
self.client.copy_volume_from_view(vol_name, snp_name)
|
|
|
|
while not self.client.snapshot_copy_task_completed(vol_name):
|
|
time.sleep(2)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
self.client.delete_snapshot_view(snp_name)
|
|
self._delete_volume(vol_name)
|
|
else:
|
|
self.client.delete_snapshot_view(snp_name)
|
|
|
|
lun_uuid = self.client.get_lun_uuid(vol_name)
|
|
return {'provider_location': 'macrosan uuid:%s' % lun_uuid}
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def create_volume_from_snapshot(self, volume, snapshot):
|
|
"""Create a volume from a snapshot."""
|
|
snapshot_volume = snapshot['volume']
|
|
provider = snapshot['provider_location']
|
|
m = re.findall(r'pointid: (\d+)', provider)
|
|
pointid = int(m[0])
|
|
|
|
vol_name = self._volume_name(volume)
|
|
snp_name = self._snapshot_name(snapshot['id'])
|
|
snp_vol_name = self._volume_name(snapshot_volume)
|
|
|
|
params = self._parse_volume_params(volume)
|
|
self._check_volume_params(params)
|
|
|
|
return self._create_volume_from_snapshot(vol_name,
|
|
volume['size'],
|
|
params,
|
|
snp_name,
|
|
pointid,
|
|
snp_vol_name,
|
|
snapshot['volume_size'])
|
|
|
|
def _create_cloned_volume(self, vol_name, vol_size, vol_params,
|
|
src_vol_name, src_vol_size, snp_name):
|
|
pointid = self._create_snapshot(snp_name,
|
|
src_vol_name,
|
|
src_vol_size)
|
|
|
|
try:
|
|
return self._create_volume_from_snapshot(vol_name,
|
|
vol_size,
|
|
vol_params,
|
|
snp_name,
|
|
pointid,
|
|
src_vol_name,
|
|
src_vol_size)
|
|
finally:
|
|
self._delete_snapshot(snp_name, src_vol_name, pointid)
|
|
|
|
@record_request_id
|
|
@utils.trace
|
|
def create_cloned_volume(self, volume, src_vref):
|
|
"""Create a clone of the specified volume."""
|
|
vol_name = volume['id']
|
|
src_vol_name = self._volume_name(src_vref)
|
|
snapshotid =\
|
|
src_vref['id'][:12] + timeutils.utcnow().strftime('%Y%m%d%H%M%S%f')
|
|
snp_name = self._snapshot_name(snapshotid)
|
|
|
|
params = self._parse_volume_params(volume)
|
|
self._check_volume_params(params)
|
|
|
|
return self._create_cloned_volume(vol_name, volume['size'], params,
|
|
src_vol_name, src_vref['size'],
|
|
snp_name)
|
|
|
|
def _extend_volume(self, name, moresize, params):
|
|
if params['replication_enabled']:
|
|
raise Exception(
|
|
'Volume %s has replication enabled, cannot extend' % name)
|
|
|
|
if params['sdas']:
|
|
self.client.suspend_dalun(name)
|
|
|
|
raids = self.client.get_raid_list_to_create_lun(self.pool,
|
|
moresize)
|
|
self.client.extend_lun(name, raids, moresize)
|
|
|
|
raids = self.sdas_client.get_raid_list_to_create_lun(self.pool,
|
|
moresize)
|
|
self.sdas_client.extend_lun(name, raids, moresize)
|
|
|
|
self.client.resume_dalun(name)
|
|
else:
|
|
raids = self.client.get_raid_list_to_create_lun(self.pool,
|
|
moresize)
|
|
self.client.extend_lun(name, raids, moresize)
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def extend_volume(self, volume, new_size):
|
|
"""Extend a volume."""
|
|
name = self._volume_name(volume)
|
|
moresize = self._size_str_to_int(new_size - int(volume['size']))
|
|
params = self._parse_volume_params(volume)
|
|
self._extend_volume(name, moresize, params)
|
|
|
|
def ensure_export(self, context, volume):
|
|
"""Synchronously recreates an export for a volume."""
|
|
pass
|
|
|
|
def create_export(self, context, volume, connector):
|
|
"""Export the volume."""
|
|
pass
|
|
|
|
def remove_export(self, context, volume):
|
|
"""Remove an export for a volume."""
|
|
pass
|
|
|
|
@record_request_id
|
|
def _update_volume_stats(self):
|
|
data = {}
|
|
pool = {}
|
|
|
|
total, free, thin_unalloced = self.client.get_pool_cap(self.pool)
|
|
pool['location_info'] = self.device_uuid
|
|
pool['pool_name'] = self.pool
|
|
pool['total_capacity_gb'] = total
|
|
pool['free_capacity_gb'] = free + thin_unalloced
|
|
pool['reserved_percentage'] = self.configuration.safe_get(
|
|
'reserved_percentage')
|
|
pool['max_over_subscription_ratio'] = self.configuration.safe_get(
|
|
'max_over_subscription_ratio')
|
|
pool['QoS_support'] = True
|
|
pool['multiattach'] = True
|
|
pool['lun_mode'] = True
|
|
pool['replication_mode'] = []
|
|
|
|
if self.replica_client:
|
|
pool['replication_enabled'] = 'True'
|
|
pool['replication_mode'].append('async')
|
|
|
|
if self.sdas_client:
|
|
pool['replication_enabled'] = 'True'
|
|
pool['sdas'] = 'True'
|
|
pool['replication_mode'].append('sync')
|
|
|
|
if len(pool['replication_mode']) == 0:
|
|
del pool['replication_mode']
|
|
|
|
data['pools'] = [pool]
|
|
data["volume_backend_name"] = self.volume_backend_name
|
|
data["vendor_name"] = 'MacroSAN'
|
|
data["driver_version"] = version
|
|
data["storage_protocol"] = self.storage_protocol
|
|
|
|
self._stats = data
|
|
|
|
@record_request_id
|
|
@utils.trace
|
|
def update_migrated_volume(self, ctxt, volume, new_volume,
|
|
original_volume_status=None):
|
|
"""Return model update for migrated volume."""
|
|
original_name = self._volume_name(volume)
|
|
cur_name = self._volume_name(new_volume)
|
|
|
|
if self.client.lun_exists(original_name):
|
|
self.client.backup_lun_name_to_rename_file(cur_name, original_name)
|
|
else:
|
|
if original_volume_status == 'available':
|
|
try:
|
|
self.client.rename_lun(cur_name, original_name)
|
|
except Exception:
|
|
LOG.warning('========== failed to rename '
|
|
'%(cur_name)s to %(original_name)s',
|
|
{'cur_name': cur_name,
|
|
'original_name': original_name})
|
|
|
|
name_id = new_volume['_name_id'] or new_volume['id']
|
|
return {'_name_id': name_id,
|
|
'provider_location': new_volume['provider_location']}
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
|
|
volume = snapshot['volume']
|
|
provider = snapshot['provider_location']
|
|
m = re.findall(r'pointid: (\d+)', provider)
|
|
|
|
pointid = m[0]
|
|
snp_name = self._snapshot_name(snapshot['id'])
|
|
snp_vol_name = self._volume_name(volume)
|
|
|
|
self.client.create_snapshot_view(snp_name, snp_vol_name, pointid)
|
|
|
|
try:
|
|
conn = self._initialize_connection_snapshot(snp_name, connector)
|
|
conn['data']['volume_id'] = snapshot['id']
|
|
return conn
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
self.client.delete_snapshot_view(snp_name)
|
|
|
|
def _initialize_connection_snapshot(self, snp_name, connector):
|
|
raise NotImplementedError
|
|
|
|
def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
|
|
snp_name = self._snapshot_name(snapshot['id'])
|
|
self._terminate_connection_snapshot(snp_name, connector)
|
|
self.client.delete_snapshot_view(snp_name)
|
|
|
|
def _terminate_connection_snapshot(self, snp_name, connector):
|
|
raise NotImplementedError
|
|
|
|
@record_request_id
|
|
def manage_existing_get_size(self, volume, external_ref):
|
|
__, info, __ = self._get_existing_lun_info(external_ref)
|
|
size = int(math.ceil(info['size']))
|
|
return size
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def manage_existing(self, volume, external_ref):
|
|
vol_params = self._parse_volume_params(volume)
|
|
self._check_volume_params(vol_params)
|
|
if vol_params['qos-strategy']:
|
|
raise exception.VolumeBackendAPIException(
|
|
data=_('Import qos-strategy not supported'))
|
|
|
|
pool = volume_utils.extract_host(volume.host, 'pool')
|
|
name, info, params = self._get_existing_lun_info(external_ref)
|
|
if pool != info['pool']:
|
|
msg = _("LUN %(name)s does not belong to the pool: "
|
|
"%(pool)s."), {'name': name, 'pool': pool}
|
|
raise exception.ManageExistingInvalidReference(
|
|
existing_ref=external_ref, reason=msg)
|
|
|
|
if params['sdas'] and params['replication_enabled']:
|
|
msg = _('LUN %(name)s sdas and replication '
|
|
'enabled at same time'), {'name': name}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
if replication_synced(vol_params) and params['sdas']:
|
|
params.update({'sdas': False,
|
|
'replication_mode': 'sync',
|
|
'replication_enabled': True})
|
|
|
|
def notequal(attr):
|
|
return vol_params[attr] != params[attr]
|
|
|
|
if (notequal('replication_enabled') or notequal('replication_mode') or
|
|
notequal('sdas') or notequal('lun_mode')):
|
|
msg = _("Volume type: %(vol_params)s doesn't equal "
|
|
"to existing lun: "
|
|
"%(params)s"), {'vol_params': vol_params, 'params': params}
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
rmt_client = None
|
|
if params['sdas']:
|
|
rmt_client = self.sdas_client
|
|
elif params['replication_enabled']:
|
|
rmt_client = self.replica_client
|
|
|
|
snp_res_name = self.client.get_snapshot_resource_name(name)
|
|
self.client.rename_lun(name, volume['name'])
|
|
if snp_res_name:
|
|
self.client.rename_lun(snp_res_name, 'SR-%s' % volume['id'])
|
|
|
|
if params['sdas'] or params['replication_enabled']:
|
|
snp_res_name = rmt_client.get_snapshot_resource_name(name)
|
|
rmt_client.rename_lun(name, volume['name'])
|
|
if snp_res_name:
|
|
rmt_client.rename_lun(snp_res_name, 'SR-%s' % volume['id'])
|
|
|
|
lun_uuid = self.client.get_lun_uuid(volume['name'])
|
|
return {'provider_location': 'macrosan uuid:%s' % lun_uuid}
|
|
|
|
def _get_existing_lun_info(self, external_ref):
|
|
name = external_ref.get('source-name')
|
|
if not name:
|
|
raise exception.ManageExistingInvalidReference(
|
|
existing_ref=external_ref,
|
|
reason=_('No source-name to get existing lun'))
|
|
|
|
info = self.client.get_lun_base_info(name)
|
|
|
|
params = {
|
|
'qos-strategy': '',
|
|
'replication_enabled': False,
|
|
'replication_mode': 'async',
|
|
'sdas': False,
|
|
'lun_mode': False
|
|
}
|
|
|
|
sdas = self.client.dalun_exists(name)
|
|
rep = self.client.replication_enabled(name)
|
|
params['replication_enabled'] = rep
|
|
params['sdas'] = sdas
|
|
if info['lun_mode'] == 'thin':
|
|
info['lun_mode'] = True
|
|
else:
|
|
info['lun_mode'] = False
|
|
params['lun_mode'] = info['lun_mode']
|
|
|
|
return name, info, params
|
|
|
|
def unmanage(self, volume):
|
|
pass
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def manage_existing_snapshot(self, snapshot, existing_ref):
|
|
volume = snapshot['volume']
|
|
src_name = self._get_existing_snapname(existing_ref).lstrip('_')
|
|
src_name = self._snapshot_name(src_name)
|
|
pointid = self.client.get_snapshot_pointid(volume['name'], src_name)
|
|
snap_name = self._snapshot_name(snapshot['id'])
|
|
|
|
self.client.rename_snapshot_point(volume['name'], pointid, snap_name)
|
|
return {'provider_location': 'pointid: %s' % pointid}
|
|
|
|
@record_request_id
|
|
def manage_existing_snapshot_get_size(self, snapshot, existing_ref):
|
|
volume = snapshot['volume']
|
|
return volume['size']
|
|
|
|
def _get_existing_snapname(self, external_ref):
|
|
name = external_ref.get('source-name')
|
|
if not name:
|
|
raise exception.ManageExistingInvalidReference(
|
|
existing_ref=external_ref,
|
|
reason=_('No source-name to get existing snap'))
|
|
return name
|
|
|
|
def unmanage_snapshot(self, snapshot):
|
|
pass
|
|
|
|
def migration_valid(self, volume, host):
|
|
if volume.volume_attachment:
|
|
return False
|
|
|
|
pool_name = host['capabilities'].get('pool_name', '')
|
|
if pool_name == '':
|
|
return False
|
|
|
|
device_uuid = host['capabilities']['location_info']
|
|
if device_uuid != self.device_uuid:
|
|
return False
|
|
|
|
params = self._parse_volume_params(volume)
|
|
if params['sdas'] or params['replication_enabled']:
|
|
return False
|
|
|
|
return True
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def migrate_volume(self, ctxt, volume, host):
|
|
if not self.migration_valid(volume, host):
|
|
return False, None
|
|
|
|
size = self._size_str_to_int(volume['size'])
|
|
params = self._parse_volume_params(volume)
|
|
name = str(uuid.uuid4())
|
|
src_name = self._volume_name(volume)
|
|
owner = self.client.get_lun_sp(src_name)
|
|
pool = host['capabilities'].get('pool_name', self.pool)
|
|
|
|
LOG.info('Migrating volume: %(volume)s, '
|
|
'host: %(host)s, '
|
|
'backend: %(volume_backend_name)s',
|
|
{'volume': src_name,
|
|
'host': host,
|
|
'volume_backend_name': self.volume_backend_name})
|
|
self._create_volume(name, size, params, owner, pool)
|
|
|
|
res_sz = int(max(int(size) * self.snapshot_resource_ratio, 1))
|
|
src_snp_res_exists = self.client.snapshot_resource_exists(src_name)
|
|
if not src_snp_res_exists:
|
|
raids = self.client.get_raid_list_to_create_lun(self.pool, res_sz)
|
|
self.client.create_snapshot_resource(src_name, raids, res_sz)
|
|
|
|
snp_res_exists = self.client.snapshot_resource_exists(name)
|
|
if not snp_res_exists:
|
|
raids = self.client.get_raid_list_to_create_lun(pool, res_sz)
|
|
self.client.create_snapshot_resource(name, raids, res_sz)
|
|
|
|
self.client.start_localclone_lun(src_name, name)
|
|
while not self.client.localclone_completed(name):
|
|
time.sleep(2)
|
|
self.client.stop_localclone_lun(name)
|
|
|
|
if not snp_res_exists:
|
|
self.client.delete_snapshot_resource(name)
|
|
if not src_snp_res_exists:
|
|
self.client.delete_snapshot_resource(src_name)
|
|
|
|
self._delete_volume(src_name, params)
|
|
self.client.rename_lun(name, src_name)
|
|
|
|
lun_uuid = self.client.get_lun_uuid(src_name)
|
|
return True, {'provider_location': 'macrosan uuid:%s' % lun_uuid}
|
|
|
|
def force_terminate_connection(self, name, force_connected=False):
|
|
it_list = self.client.get_lun_it(name)
|
|
it_list = [it for it in it_list
|
|
if (force_connected or not it['connected'])]
|
|
if len(it_list) > 0:
|
|
for it in it_list:
|
|
self.client.unmap_lun_to_it(name,
|
|
it['initiator'],
|
|
it['port'])
|
|
|
|
|
|
@interface.volumedriver
|
|
class MacroSANISCSIDriver(MacroSANBaseDriver, driver.ISCSIDriver):
|
|
"""ISCSI driver for MacroSan storage arrays.
|
|
|
|
Version history:
|
|
|
|
.. code-block:: none
|
|
|
|
1.0.0 - Initial driver
|
|
1.0.1 - Adjust some log level and text prompts; Remove some useless
|
|
functions; Add Cinder trace decorator. #1837920
|
|
"""
|
|
VERSION = "1.0.1"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Initialize the driver."""
|
|
super(MacroSANISCSIDriver, self).__init__(*args, **kwargs)
|
|
self.storage_protocol = 'iSCSI'
|
|
|
|
def _do_setup(self):
|
|
ports = self.client.get_iscsi_ports()
|
|
for port in ports:
|
|
if port['port_name'] == '' and port['ip'] != '0':
|
|
self.client.create_target(port['port'], type='iscsi')
|
|
|
|
if self.sdas_client:
|
|
ports = self.sdas_client.get_iscsi_ports()
|
|
for port in ports:
|
|
if port['port_name'] == '' and port['ip'] != '0':
|
|
self.sdas_client.create_target(port['port'], type='iscsi')
|
|
|
|
def _get_iscsi_ports(self, dev_client, host):
|
|
ha_state = dev_client.get_ha_state()
|
|
|
|
if host in self.client_info:
|
|
iscsi_sp1 = self.client_info[host]['sp1_port']
|
|
iscsi_sp2 = self.client_info[host]['sp2_port']
|
|
else:
|
|
iscsi_sp1 = self.client_info['default']['sp1_port']
|
|
iscsi_sp2 = self.client_info['default']['sp2_port']
|
|
ports = []
|
|
if ha_state['sp1'] in ['single', 'double', 'idle']:
|
|
ports.extend(iscsi_sp1.split(','))
|
|
|
|
if ha_state['sp2'] in ['single', 'double', 'idle']:
|
|
ports.extend(iscsi_sp2.split(','))
|
|
|
|
all_ports = {p['port']: p for p in dev_client.get_iscsi_ports()}
|
|
|
|
return [all_ports[p] for p in ports]
|
|
|
|
def _map_initr_tgt(self, dev_client, itl_client_name, initr, ports):
|
|
if not dev_client.get_client(itl_client_name):
|
|
dev_client.create_client(itl_client_name)
|
|
|
|
if not dev_client.initiator_exists(initr):
|
|
dev_client.create_initiator(initr, itl_client_name, type='iscsi')
|
|
|
|
if not dev_client.is_initiator_mapped_to_client(initr,
|
|
itl_client_name):
|
|
dev_client.map_initiator_to_client(initr, itl_client_name)
|
|
|
|
for p in ports:
|
|
port_name = p['port_name']
|
|
dev_client.map_target_to_initiator(port_name, initr)
|
|
|
|
def _unmap_itl(self, dev_client, itl_client_name,
|
|
wwns, ports, volume_name):
|
|
wwn = wwns[0]
|
|
for p in ports:
|
|
port_name = p['port_name']
|
|
dev_client.unmap_lun_to_it(volume_name, wwn, port_name)
|
|
|
|
def _map_itl(self, dev_client, wwn, ports, volume_name, hint_lun_id):
|
|
lun_id = hint_lun_id
|
|
exists = False
|
|
for p in ports:
|
|
port_name = p['port_name']
|
|
exists = dev_client.map_lun_to_it(volume_name, wwn, port_name,
|
|
hint_lun_id)
|
|
|
|
if exists and lun_id == hint_lun_id:
|
|
lun_id = self.client.get_lun_id(wwn, port_name, volume_name)
|
|
|
|
return lun_id
|
|
|
|
def _get_unused_lun_id(self, wwn, dev_client, ports,
|
|
sdas_client, sdas_ports):
|
|
id_list = set(range(0, 511))
|
|
for p in ports:
|
|
port_name = p['port_name']
|
|
tmp_list = dev_client.get_it_unused_id_list('iscsi', wwn,
|
|
port_name)
|
|
id_list = id_list.intersection(tmp_list)
|
|
|
|
for p in sdas_ports:
|
|
port_name = p['port_name']
|
|
tmp_list = sdas_client.get_it_unused_id_list('iscsi', wwn,
|
|
port_name)
|
|
id_list = id_list.intersection(tmp_list)
|
|
|
|
return id_list.pop()
|
|
|
|
def _initialize_connection(self, name, vol_params, host, wwns):
|
|
client_name = self._get_client_name(host)
|
|
wwn = wwns[0]
|
|
LOG.debug('initialize_connection, initiator: %(wwpns)s,'
|
|
'volume name: %(volume)s.',
|
|
{'wwpns': wwns, 'volume': name})
|
|
|
|
ports = self._get_iscsi_ports(self.client, host)
|
|
self._map_initr_tgt(self.client, client_name, wwn, ports)
|
|
|
|
if vol_params['sdas']:
|
|
sdas_ports = self._get_iscsi_ports(self.sdas_client, host)
|
|
self._map_initr_tgt(self.sdas_client, client_name, wwn, sdas_ports)
|
|
lun_id = self._get_unused_lun_id(wwn, self.client, ports,
|
|
self.sdas_client, sdas_ports)
|
|
|
|
self._map_itl(self.sdas_client, wwn, sdas_ports, name, lun_id)
|
|
|
|
lun_id = self._map_itl(self.client, wwn, ports, name, lun_id)
|
|
|
|
ports = ports + sdas_ports
|
|
else:
|
|
lun_id = self._get_unused_lun_id(wwn, self.client, ports, None, {})
|
|
lun_id = self._map_itl(self.client, wwn, ports, name, lun_id)
|
|
|
|
properties = {'target_discovered': False,
|
|
'target_portal': '%s:3260' % ports[0]['ip'],
|
|
'target_iqn': ports[0]['target'],
|
|
'target_lun': lun_id,
|
|
'target_iqns': [p['target'] for p in ports],
|
|
'target_portals': ['%s:3260' % p['ip'] for p in ports],
|
|
'target_luns': [lun_id] * len(ports)}
|
|
|
|
LOG.info('initialize_connection, iSCSI properties: %(properties)s',
|
|
{'properties': properties})
|
|
return {'driver_volume_type': 'iscsi', 'data': properties}
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def initialize_connection(self, volume, connector):
|
|
"""Allow connection to connector and return connection info."""
|
|
|
|
name = self._volume_name(volume)
|
|
params = self._parse_volume_params(volume)
|
|
conn = self._initialize_connection(name, params, connector['host'],
|
|
[connector['initiator']])
|
|
conn['data']['volume_id'] = volume['id']
|
|
return conn
|
|
|
|
def _unmap_initr_tgt(self, dev_client, itl_client_name, wwn):
|
|
for p in dev_client.get_iscsi_ports():
|
|
port_name = p['port_name']
|
|
if dev_client.it_exists(wwn, port_name):
|
|
dev_client.unmap_target_from_initiator(port_name, wwn)
|
|
|
|
if dev_client.initiator_exists(wwn):
|
|
dev_client.unmap_initiator_from_client(wwn, itl_client_name)
|
|
dev_client.delete_initiator(wwn)
|
|
|
|
def _terminate_connection(self, name, volume_params, host, wwns):
|
|
client_name = self._get_client_name(host)
|
|
ports = self._get_iscsi_ports(self.client, host)
|
|
|
|
self._unmap_itl(self.client, client_name, wwns, ports, name)
|
|
if volume_params['sdas']:
|
|
self._unmap_itl(self.sdas_client, client_name, wwns, ports, name)
|
|
|
|
data = dict()
|
|
data['ports'] = ports
|
|
data['client'] = client_name
|
|
return {'driver_volume_type': 'iSCSI', 'data': data}
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def terminate_connection(self, volume, connector, **kwargs):
|
|
"""Disallow connection from connector."""
|
|
|
|
name = self._volume_name(volume)
|
|
conn = None
|
|
if not connector:
|
|
self.force_terminate_connection(name, True)
|
|
else:
|
|
params = self._parse_volume_params(volume)
|
|
conn = self._terminate_connection(name, params, connector['host'],
|
|
[connector['initiator']])
|
|
return conn
|
|
|
|
def _initialize_connection_snapshot(self, snp_name, connector):
|
|
return self._initialize_connection(snp_name, None, connector['host'],
|
|
[connector['initiator']])
|
|
|
|
def _terminate_connection_snapshot(self, snp_name, connector):
|
|
return self._terminate_connection(snp_name, None, connector['host'],
|
|
[connector['initiator']])
|
|
|
|
|
|
@interface.volumedriver
|
|
class MacroSANFCDriver(MacroSANBaseDriver, driver.FibreChannelDriver):
|
|
"""FC driver for MacroSan storage arrays.
|
|
|
|
Version history:
|
|
|
|
.. code-block:: none
|
|
|
|
1.0.0 - Initial driver
|
|
1.0.1 - Adjust some log level and text prompts; Remove some useless
|
|
functions; Add Cinder trace decorator. #1837920
|
|
"""
|
|
VERSION = "1.0.1"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Initialize the driver."""
|
|
super(MacroSANFCDriver, self).__init__(*args, **kwargs)
|
|
self.storage_protocol = 'FC'
|
|
self.fcsan_lookup_service = None
|
|
self.use_sp_port_nr = self.configuration.macrosan_fc_use_sp_port_nr
|
|
self.keep_mapped_ports = \
|
|
self.configuration.macrosan_fc_keep_mapped_ports
|
|
|
|
def _do_setup(self):
|
|
self.fcsan_lookup_service = fczm_utils.create_lookup_service()
|
|
|
|
ports = self.client.get_fc_ports()
|
|
for port in ports:
|
|
if port['port_name'] == '':
|
|
self.client.create_target(port['port'])
|
|
|
|
if self.sdas_client:
|
|
ports = self.sdas_client.get_fc_ports()
|
|
for port in ports:
|
|
if port['port_name'] == '':
|
|
self.sdas_client.create_target(port['port'])
|
|
|
|
def _strip_wwn_colon(self, wwn_str):
|
|
return wwn_str.replace(':', '')
|
|
|
|
def _format_wwn_with_colon(self, wwn_str):
|
|
wwn_str = wwn_str.replace(":", "")
|
|
return (':'.join([wwn_str[i:i + 2]
|
|
for i in range(0, len(wwn_str), 2)])).lower()
|
|
|
|
def _select_fc_ports(self, ports_in_storage, ports_in_fabric):
|
|
selected = []
|
|
for sp in [1, 2]:
|
|
n = 0
|
|
for p in ports_in_storage:
|
|
if (p['sp'] == sp
|
|
and p['online'] == 1 and p['wwn'] in ports_in_fabric):
|
|
selected.append({'port_name': p['port_name'],
|
|
'wwn': p['wwn']})
|
|
n += 1
|
|
if n >= self.use_sp_port_nr:
|
|
break
|
|
return selected
|
|
|
|
def _get_initr_port_map(self, dev_client, wwns):
|
|
initr_port_map = {}
|
|
ports_in_storage = dev_client.get_fc_ports()
|
|
if self.fcsan_lookup_service is not None:
|
|
mapping = (self.fcsan_lookup_service
|
|
.get_device_mapping_from_network(
|
|
wwns, [p['wwn'] for p in ports_in_storage]))
|
|
|
|
for fabric in mapping:
|
|
wwns = mapping[fabric]['target_port_wwn_list']
|
|
mapping[fabric]['target_port_wwn_list'] = (
|
|
[self._format_wwn_with_colon(wwn) for wwn in wwns])
|
|
wwns = mapping[fabric]['initiator_port_wwn_list']
|
|
mapping[fabric]['initiator_port_wwn_list'] = (
|
|
[self._format_wwn_with_colon(wwn) for wwn in wwns])
|
|
|
|
for fabric in mapping:
|
|
ports_in_fabric = mapping[fabric]['target_port_wwn_list']
|
|
selected_ports = self._select_fc_ports(ports_in_storage,
|
|
ports_in_fabric)
|
|
|
|
for initr in mapping[fabric]['initiator_port_wwn_list']:
|
|
initr_port_map[initr] = selected_ports
|
|
else:
|
|
initr_port_map = {}
|
|
for wwn in wwns:
|
|
for port in ports_in_storage:
|
|
if port['initr'] == wwn:
|
|
initr_port_map[wwn] = [port]
|
|
break
|
|
|
|
return initr_port_map
|
|
|
|
def _map_initr_tgt_do(self, dev_client, itl_client_name,
|
|
initr_port_map, mapped_ports):
|
|
for wwn in initr_port_map:
|
|
if wwn in mapped_ports:
|
|
continue
|
|
|
|
if not dev_client.initiator_exists(wwn):
|
|
dev_client.create_initiator(wwn, wwn)
|
|
|
|
if not dev_client.is_initiator_mapped_to_client(wwn,
|
|
itl_client_name):
|
|
dev_client.map_initiator_to_client(wwn, itl_client_name)
|
|
|
|
for p in initr_port_map[wwn]:
|
|
port_name = p['port_name']
|
|
dev_client.map_target_to_initiator(port_name, wwn)
|
|
|
|
def _unmap_initr_tgt(self, dev_client, client_name, mapped_ports):
|
|
for wwn in mapped_ports:
|
|
for p in mapped_ports[wwn]:
|
|
port_name = p['port_name']
|
|
if dev_client.it_exists(wwn, port_name):
|
|
dev_client.unmap_target_from_initiator(port_name, wwn)
|
|
|
|
if dev_client.initiator_exists(wwn):
|
|
dev_client.unmap_initiator_from_client(wwn, client_name)
|
|
dev_client.delete_initiator(wwn)
|
|
|
|
def _map_initr_tgt(self, dev_client, itl_client_name, wwns):
|
|
if not dev_client.get_client(itl_client_name):
|
|
dev_client.create_client(itl_client_name)
|
|
|
|
initr_port_map = {}
|
|
mapped_ports = dev_client.get_fc_initr_mapped_ports(wwns)
|
|
has_port_not_mapped = not all(wwn in mapped_ports for wwn in wwns)
|
|
if has_port_not_mapped:
|
|
initr_port_map = self._get_initr_port_map(dev_client, wwns)
|
|
|
|
initr_port_map.update(mapped_ports)
|
|
|
|
if has_port_not_mapped:
|
|
self._map_initr_tgt_do(dev_client, itl_client_name,
|
|
initr_port_map, mapped_ports)
|
|
return has_port_not_mapped, initr_port_map
|
|
|
|
def _map_itl(self, dev_client, initr_port_map, volume_name, hint_lun_id):
|
|
lun_id = hint_lun_id
|
|
exists = False
|
|
for wwn in initr_port_map:
|
|
for p in initr_port_map[wwn]:
|
|
port_name = p['port_name']
|
|
exists = dev_client.map_lun_to_it(volume_name, wwn,
|
|
port_name, lun_id)
|
|
|
|
if exists and lun_id == hint_lun_id:
|
|
lun_id = dev_client.get_lun_id(wwn, port_name, volume_name)
|
|
return lun_id
|
|
|
|
def _get_unused_lun_id(self, dev_client, initr_port_map,
|
|
sdas_client, sdas_initr_port_map):
|
|
id_list = set(range(0, 511))
|
|
for wwn in initr_port_map:
|
|
for p in initr_port_map[wwn]:
|
|
port_name = p['port_name']
|
|
tmp_list = dev_client.get_it_unused_id_list('fc', wwn,
|
|
port_name)
|
|
id_list = id_list.intersection(tmp_list)
|
|
|
|
for wwn in sdas_initr_port_map:
|
|
for p in sdas_initr_port_map[wwn]:
|
|
port_name = p['port_name']
|
|
tmp_list = sdas_client.get_it_unused_id_list('fc', wwn,
|
|
port_name)
|
|
id_list = id_list.intersection(tmp_list)
|
|
|
|
return id_list.pop()
|
|
|
|
def _initialize_connection(self, name, vol_params, host, wwns):
|
|
client_name = self._get_client_name(host)
|
|
|
|
LOG.info('initialize_connection, initiator: %(wwpns)s, '
|
|
'volume name: %(volume)s.',
|
|
{'wwpns': wwns, 'volume': name})
|
|
|
|
has_port_not_mapped, initr_port_map = (
|
|
self._map_initr_tgt(self.client, client_name, wwns))
|
|
LOG.debug('initr_port_map: %(initr_port_map)s',
|
|
{'initr_port_map': initr_port_map})
|
|
|
|
if vol_params and vol_params['sdas']:
|
|
sdas_has_port_not_mapped, sdas_initr_port_map = (
|
|
self._map_initr_tgt(self.sdas_client, client_name, wwns))
|
|
lun_id = self._get_unused_lun_id(self.client, initr_port_map,
|
|
self.sdas_client,
|
|
sdas_initr_port_map)
|
|
LOG.debug('sdas_initr_port_map: %(sdas_initr_port_map)s',
|
|
{'sdas_initr_port_map': sdas_initr_port_map})
|
|
self._map_itl(self.sdas_client, sdas_initr_port_map, name, lun_id)
|
|
|
|
lun_id = self._map_itl(self.client, initr_port_map, name, lun_id)
|
|
for initr, ports in sdas_initr_port_map.items():
|
|
if len(ports):
|
|
initr_port_map[initr].extend(ports)
|
|
|
|
has_port_not_mapped = (has_port_not_mapped or
|
|
sdas_has_port_not_mapped)
|
|
else:
|
|
lun_id = self._get_unused_lun_id(self.client, initr_port_map,
|
|
None, {})
|
|
lun_id = self._map_itl(self.client, initr_port_map, name, lun_id)
|
|
|
|
tgt_wwns = list(set(self._strip_wwn_colon(p['wwn'])
|
|
for wwn in initr_port_map
|
|
for p in initr_port_map[wwn]))
|
|
tgt_wwns.sort()
|
|
|
|
properties = {'target_lun': lun_id,
|
|
'target_discovered': True,
|
|
'target_wwn': tgt_wwns}
|
|
if has_port_not_mapped and self.fcsan_lookup_service is not None:
|
|
initr_tgt_map = {}
|
|
for initr, ports in initr_port_map.items():
|
|
initr = self._strip_wwn_colon(initr)
|
|
initr_tgt_map[initr] = (
|
|
[self._strip_wwn_colon(p['wwn']) for p in ports])
|
|
|
|
properties['initiator_target_map'] = initr_tgt_map
|
|
|
|
LOG.info('initialize_connection, FC properties: %(properties)s',
|
|
{'properties': properties})
|
|
return {'driver_volume_type': 'fibre_channel', 'data': properties}
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def initialize_connection(self, volume, connector):
|
|
"""Allow connection to connector and return connection info."""
|
|
|
|
name = self._volume_name(volume)
|
|
params = self._parse_volume_params(volume)
|
|
wwns = [self._format_wwn_with_colon(wwns)
|
|
for wwns in connector['wwnns']]
|
|
conn = self._initialize_connection(name, params,
|
|
connector['host'], wwns)
|
|
conn['data']['volume_id'] = volume['id']
|
|
fczm_utils.add_fc_zone(conn)
|
|
return conn
|
|
|
|
def _unmap_itl(self, dev_client, name, itl_client_name, wwns):
|
|
mapped_ports = dev_client.get_fc_initr_mapped_ports(wwns)
|
|
if len(mapped_ports) == 0:
|
|
return [], {}
|
|
|
|
for wwn, ports in mapped_ports.items():
|
|
for p in ports:
|
|
port_name = p['port_name']
|
|
dev_client.unmap_lun_to_it(name, wwn, port_name)
|
|
|
|
ports, initr_tgt_map = [], {}
|
|
if (not self.keep_mapped_ports and
|
|
not dev_client.has_initiators_mapped_any_lun(wwns)):
|
|
mapped_ports = dev_client.get_fc_initr_mapped_ports(wwns)
|
|
initr_tgt_map = {self._strip_wwn_colon(wwn):
|
|
[self._strip_wwn_colon(p['wwn'])
|
|
for p in mapped_ports[wwn]] for wwn in wwns}
|
|
ports = list(set(self._strip_wwn_colon(p['wwn'])
|
|
for ports in mapped_ports.values()
|
|
for p in ports))
|
|
self._unmap_initr_tgt(dev_client, itl_client_name, mapped_ports)
|
|
if self.fcsan_lookup_service is None:
|
|
initr_tgt_map = {}
|
|
|
|
return ports, initr_tgt_map
|
|
|
|
def _terminate_connection(self, name, vol_params, host, wwns):
|
|
client_name = self._get_client_name(host)
|
|
ports, initr_tgt_map = self._unmap_itl(self.client, name,
|
|
client_name, wwns)
|
|
if vol_params and vol_params['sdas']:
|
|
sdas_ports, sdas_initr_tgt_map = (
|
|
self._unmap_itl(self.sdas_client, name,
|
|
client_name, wwns))
|
|
ports.extend(sdas_ports)
|
|
for initr, tgt_wwns in sdas_initr_tgt_map.items():
|
|
if len(tgt_wwns):
|
|
initr_tgt_map[initr].extend(tgt_wwns)
|
|
|
|
data = {}
|
|
if ports:
|
|
data['target_wwn'] = ports
|
|
if initr_tgt_map:
|
|
data['initiator_target_map'] = initr_tgt_map
|
|
LOG.info('terminate_connection, data: %(data)s', {'data': data})
|
|
return {'driver_volume_type': 'fibre_channel', 'data': data}
|
|
|
|
@synchronized(lock_name)
|
|
@record_request_id
|
|
@utils.trace
|
|
def terminate_connection(self, volume, connector, **kwargs):
|
|
"""Disallow connection from connector."""
|
|
|
|
name = self._volume_name(volume)
|
|
conn = None
|
|
if not connector:
|
|
self.force_terminate_connection(name, True)
|
|
conn = {'driver_volume_type': 'fibre_channel', 'data': {}}
|
|
else:
|
|
params = self._parse_volume_params(volume)
|
|
wwns = [self._format_wwn_with_colon(wwns)
|
|
for wwns in connector['wwpns']]
|
|
attachments = volume.volume_attachment
|
|
hostnum = 0
|
|
for i in attachments:
|
|
if connector['host'] == i['attached_host']:
|
|
hostnum += 1
|
|
if hostnum > 1:
|
|
pass
|
|
else:
|
|
conn = self._terminate_connection(name, params,
|
|
connector['host'], wwns)
|
|
fczm_utils.remove_fc_zone(conn)
|
|
return conn
|
|
|
|
def _initialize_connection_snapshot(self, snp_name, connector):
|
|
wwns = [self._format_wwn_with_colon(wwns)
|
|
for wwns in connector['wwpns']]
|
|
return self._initialize_connection(snp_name, None, connector['host'],
|
|
wwns)
|
|
|
|
def _terminate_connection_snapshot(self, snp_name, connector):
|
|
wwns = [self._format_wwn_with_colon(wwns)
|
|
for wwns in connector['wwpns']]
|
|
return self._terminate_connection(snp_name, None, connector['host'],
|
|
wwns)
|