825 lines
31 KiB
Python
825 lines
31 KiB
Python
# Copyright (C) 2014, Hitachi, Ltd.
|
|
#
|
|
# 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.
|
|
"""
|
|
Common class for Hitachi storage drivers.
|
|
|
|
"""
|
|
|
|
import re
|
|
import threading
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
import six
|
|
|
|
from cinder import exception
|
|
from cinder.i18n import _LE, _LI, _LW
|
|
from cinder import utils
|
|
from cinder.volume.drivers.hitachi import hbsd_basiclib as basic_lib
|
|
from cinder.volume.drivers.hitachi import hbsd_horcm as horcm
|
|
from cinder.volume.drivers.hitachi import hbsd_snm2 as snm2
|
|
from cinder.volume import utils as volume_utils
|
|
|
|
"""
|
|
Version history:
|
|
1.0.0 - Initial driver
|
|
1.1.0 - Add manage_existing/manage_existing_get_size/unmanage methods
|
|
"""
|
|
VERSION = '1.1.0'
|
|
|
|
PARAM_RANGE = {
|
|
'hitachi_copy_check_interval': {'min': 1, 'max': 600},
|
|
'hitachi_async_copy_check_interval': {'min': 1, 'max': 600},
|
|
'hitachi_copy_speed': {'min': 1, 'max': 15},
|
|
}
|
|
|
|
DEFAULT_LDEV_RANGE = [0, 65535]
|
|
|
|
COPY_METHOD = ('FULL', 'THIN')
|
|
VALID_DP_VOLUME_STATUS = ['available', 'in-use']
|
|
VALID_V_VOLUME_STATUS = ['available']
|
|
SYSTEM_LOCK_FILE = basic_lib.LOCK_DIR + 'system'
|
|
SERVICE_LOCK_PATH_BASE = basic_lib.LOCK_DIR + 'service_'
|
|
STORAGE_LOCK_PATH_BASE = basic_lib.LOCK_DIR + 'storage_'
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
volume_opts = [
|
|
cfg.StrOpt('hitachi_serial_number',
|
|
help='Serial number of storage system'),
|
|
cfg.StrOpt('hitachi_unit_name',
|
|
help='Name of an array unit'),
|
|
cfg.IntOpt('hitachi_pool_id',
|
|
help='Pool ID of storage system'),
|
|
cfg.IntOpt('hitachi_thin_pool_id',
|
|
help='Thin pool ID of storage system'),
|
|
cfg.StrOpt('hitachi_ldev_range',
|
|
help='Range of logical device of storage system'),
|
|
cfg.StrOpt('hitachi_default_copy_method',
|
|
default='FULL',
|
|
help='Default copy method of storage system'),
|
|
cfg.IntOpt('hitachi_copy_speed',
|
|
default=3,
|
|
help='Copy speed of storage system'),
|
|
cfg.IntOpt('hitachi_copy_check_interval',
|
|
default=3,
|
|
help='Interval to check copy'),
|
|
cfg.IntOpt('hitachi_async_copy_check_interval',
|
|
default=10,
|
|
help='Interval to check copy asynchronously'),
|
|
cfg.StrOpt('hitachi_target_ports',
|
|
help='Control port names for HostGroup or iSCSI Target'),
|
|
cfg.StrOpt('hitachi_group_range',
|
|
help='Range of group number'),
|
|
cfg.BoolOpt('hitachi_group_request',
|
|
default=False,
|
|
secret=True,
|
|
help='Request for creating HostGroup or iSCSI Target'),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(volume_opts)
|
|
|
|
|
|
class TryLock(object):
|
|
|
|
def __init__(self):
|
|
self.lock = threading.RLock()
|
|
self.desc = None
|
|
|
|
def set_desc(self, description):
|
|
self.desc = description
|
|
|
|
def __enter__(self):
|
|
if not self.lock.acquire(False):
|
|
msg = basic_lib.output_err(660, desc=self.desc)
|
|
raise exception.HBSDError(message=msg)
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
self.lock.release()
|
|
|
|
|
|
class HBSDCommon(object):
|
|
|
|
def __init__(self, conf, parent, context, db):
|
|
self.configuration = conf
|
|
self.generated_from = parent
|
|
self.context = context
|
|
self.db = db
|
|
|
|
self.system_lock_file = SYSTEM_LOCK_FILE
|
|
self.service_lock_file = '%s%s' % (SERVICE_LOCK_PATH_BASE,
|
|
conf.config_group)
|
|
if conf.hitachi_serial_number:
|
|
self.storage_lock_file = '%s%s' % (STORAGE_LOCK_PATH_BASE,
|
|
six.text_type(
|
|
conf.hitachi_serial_number))
|
|
elif conf.hitachi_unit_name:
|
|
self.storage_lock_file = '%s%s' % (STORAGE_LOCK_PATH_BASE,
|
|
six.text_type(
|
|
conf.hitachi_unit_name))
|
|
|
|
self.storage_obj_lock = threading.Lock()
|
|
self.volinfo_lock = threading.Lock()
|
|
self.volume_info = {}
|
|
self.output_first = True
|
|
|
|
def get_volume(self, volume_id):
|
|
return self.db.volume_get(self.context, volume_id)
|
|
|
|
def get_volume_metadata(self, volume_id):
|
|
return self.db.volume_metadata_get(self.context, volume_id)
|
|
|
|
def get_snapshot_metadata(self, snapshot_id):
|
|
return self.db.snapshot_metadata_get(self.context, snapshot_id)
|
|
|
|
def _update_volume_metadata(self, volume_id, volume_metadata):
|
|
self.db.volume_metadata_update(self.context, volume_id,
|
|
volume_metadata, False)
|
|
|
|
def get_ldev(self, obj):
|
|
if not obj:
|
|
return None
|
|
|
|
ldev = obj.get('provider_location')
|
|
if not ldev or not ldev.isdigit():
|
|
return None
|
|
else:
|
|
return int(ldev)
|
|
|
|
def get_value(self, obj, name, key):
|
|
if not obj:
|
|
return None
|
|
|
|
if obj.get(name):
|
|
if isinstance(obj[name], dict):
|
|
return obj[name].get(key)
|
|
else:
|
|
for i in obj[name]:
|
|
if i['key'] == key:
|
|
return i['value']
|
|
return None
|
|
|
|
def get_is_vvol(self, obj, name):
|
|
return self.get_value(obj, name, 'type') == 'V-VOL'
|
|
|
|
def get_volume_is_vvol(self, volume):
|
|
return self.get_is_vvol(volume, 'volume_metadata')
|
|
|
|
def get_snapshot_is_vvol(self, snapshot):
|
|
return self.get_is_vvol(snapshot, 'metadata')
|
|
|
|
def get_copy_method(self, volume):
|
|
method = self.get_value(volume, 'volume_metadata', 'copy_method')
|
|
if method:
|
|
if method not in COPY_METHOD:
|
|
msg = basic_lib.output_err(602, meta='copy_method')
|
|
raise exception.HBSDError(message=msg)
|
|
elif (method == 'THIN'
|
|
and self.configuration.hitachi_thin_pool_id is None):
|
|
msg = basic_lib.output_err(601, param='hitachi_thin_pool_id')
|
|
raise exception.HBSDError(message=msg)
|
|
else:
|
|
method = self.configuration.hitachi_default_copy_method
|
|
return method
|
|
|
|
def _string2int(self, num):
|
|
if not num:
|
|
return None
|
|
if num.isdigit():
|
|
return int(num, 10)
|
|
if not re.match(r'\w\w:\w\w:\w\w', num):
|
|
return None
|
|
|
|
try:
|
|
num = int(num.replace(':', ''), 16)
|
|
except ValueError:
|
|
return None
|
|
|
|
return num
|
|
|
|
def _range2list(self, conf, param):
|
|
str = getattr(conf, param)
|
|
lists = str.split('-')
|
|
if len(lists) != 2:
|
|
msg = basic_lib.output_err(601, param=param)
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
first_type = None
|
|
for i in range(len(lists)):
|
|
if lists[i].isdigit():
|
|
lists[i] = int(lists[i], 10)
|
|
if first_type == 'hex':
|
|
msg = basic_lib.output_err(601, param=param)
|
|
raise exception.HBSDError(message=msg)
|
|
first_type = 'dig'
|
|
else:
|
|
if (first_type == 'dig'
|
|
or not re.match('\w\w:\w\w:\w\w', lists[i])):
|
|
msg = basic_lib.output_err(601, param=param)
|
|
raise exception.HBSDError(message=msg)
|
|
try:
|
|
lists[i] = int(lists[i].replace(':', ''), 16)
|
|
first_type = 'hex'
|
|
except Exception:
|
|
msg = basic_lib.output_err(601, param=param)
|
|
raise exception.HBSDError(message=msg)
|
|
if lists[0] > lists[1]:
|
|
msg = basic_lib.output_err(601, param=param)
|
|
raise exception.HBSDError(message=msg)
|
|
return lists
|
|
|
|
def output_param_to_log(self, storage_protocol):
|
|
essential_inherited_param = ['volume_backend_name', 'volume_driver']
|
|
conf = self.configuration
|
|
|
|
LOG.info(basic_lib.set_msg(1, config_group=conf.config_group))
|
|
version = self.command.get_comm_version()
|
|
if conf.hitachi_unit_name:
|
|
prefix = 'HSNM2 version'
|
|
else:
|
|
prefix = 'RAID Manager version'
|
|
LOG.info(_LI('\t%(prefix)-35s : %(version)s'),
|
|
{'prefix': prefix, 'version': version})
|
|
for param in essential_inherited_param:
|
|
value = conf.safe_get(param)
|
|
LOG.info(_LI('\t%(param)-35s : %(value)s'),
|
|
{'param': param, 'value': value})
|
|
for opt in volume_opts:
|
|
if not opt.secret:
|
|
value = getattr(conf, opt.name)
|
|
LOG.info(_LI('\t%(name)-35s : %(value)s'),
|
|
{'name': opt.name, 'value': value})
|
|
|
|
if storage_protocol == 'iSCSI':
|
|
value = getattr(conf, 'hitachi_group_request')
|
|
LOG.info(_LI('\t%(request)-35s : %(value)s'),
|
|
{'request': 'hitachi_group_request', 'value': value})
|
|
|
|
def check_param(self):
|
|
conf = self.configuration
|
|
|
|
if conf.hitachi_unit_name and conf.hitachi_serial_number:
|
|
msg = basic_lib.output_err(604)
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
if not conf.hitachi_unit_name and not conf.hitachi_serial_number:
|
|
msg = basic_lib.output_err(605)
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
if conf.hitachi_pool_id is None:
|
|
msg = basic_lib.output_err(601, param='hitachi_pool_id')
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
for param in PARAM_RANGE.keys():
|
|
_value = getattr(conf, param)
|
|
if (_value and
|
|
(not PARAM_RANGE[param]['min'] <= _value <=
|
|
PARAM_RANGE[param]['max'])):
|
|
msg = basic_lib.output_err(601, param=param)
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
if conf.hitachi_default_copy_method not in COPY_METHOD:
|
|
msg = basic_lib.output_err(601,
|
|
param='hitachi_default_copy_method')
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
if (conf.hitachi_default_copy_method == 'THIN'
|
|
and conf.hitachi_thin_pool_id is None):
|
|
msg = basic_lib.output_err(601, param='hitachi_thin_pool_id')
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
for param in ('hitachi_ldev_range', 'hitachi_group_range'):
|
|
if not getattr(conf, param):
|
|
continue
|
|
else:
|
|
_value = self._range2list(conf, param)
|
|
setattr(conf, param, _value)
|
|
|
|
if conf.hitachi_target_ports:
|
|
conf.hitachi_target_ports = conf.hitachi_target_ports.split(',')
|
|
|
|
for opt in volume_opts:
|
|
getattr(conf, opt.name)
|
|
|
|
if conf.hitachi_unit_name:
|
|
self.command = snm2.HBSDSNM2(conf)
|
|
else:
|
|
conf.append_config_values(horcm.volume_opts)
|
|
self.command = horcm.HBSDHORCM(conf)
|
|
self.command.check_param()
|
|
self.pair_flock = self.command.set_pair_flock()
|
|
self.horcmgr_flock = self.command.set_horcmgr_flock()
|
|
|
|
def create_lock_file(self):
|
|
basic_lib.create_empty_file(self.system_lock_file)
|
|
basic_lib.create_empty_file(self.service_lock_file)
|
|
basic_lib.create_empty_file(self.storage_lock_file)
|
|
self.command.create_lock_file()
|
|
|
|
def _add_ldev(self, volume_num, capacity, pool_id, is_vvol):
|
|
self.command.comm_add_ldev(pool_id, volume_num, capacity, is_vvol)
|
|
|
|
def _get_unused_volume_num(self, ldev_range):
|
|
return self.command.get_unused_ldev(ldev_range)
|
|
|
|
def add_volinfo(self, ldev, id=None, type='volume'):
|
|
with self.volinfo_lock:
|
|
if ldev not in self.volume_info:
|
|
self.init_volinfo(self.volume_info, ldev)
|
|
if id:
|
|
desc = '%s %s' % (type, id)
|
|
self.volume_info[ldev]['in_use'].set_desc(desc)
|
|
|
|
def delete_pair(self, ldev, all_split=True, is_vvol=None):
|
|
paired_info = self.command.get_paired_info(ldev)
|
|
LOG.debug('paired_info: %s', paired_info)
|
|
pvol = paired_info['pvol']
|
|
svols = paired_info['svol']
|
|
driver = self.generated_from
|
|
restart = False
|
|
svol_list = []
|
|
try:
|
|
if pvol is None:
|
|
return
|
|
elif pvol == ldev:
|
|
for svol in svols[:]:
|
|
if svol['is_vvol'] or svol['status'] != basic_lib.PSUS:
|
|
continue
|
|
|
|
self.command.delete_pair(pvol, svol['lun'], False)
|
|
restart = True
|
|
driver.pair_terminate_connection(svol['lun'])
|
|
svols.remove(svol)
|
|
|
|
if all_split and svols:
|
|
svol_list.append(six.text_type(svols[0]['lun']))
|
|
for svol in svols[1:]:
|
|
svol_list.append(', %d' % svol['lun'])
|
|
|
|
msg = basic_lib.output_err(616, pvol=pvol,
|
|
svol=''.join(svol_list))
|
|
raise exception.HBSDBusy(message=msg)
|
|
|
|
if not svols:
|
|
driver.pair_terminate_connection(pvol)
|
|
|
|
else:
|
|
self.add_volinfo(pvol)
|
|
if not self.volume_info[pvol]['in_use'].lock.acquire(False):
|
|
desc = self.volume_info[pvol]['in_use'].desc
|
|
msg = basic_lib.output_err(660, desc=desc)
|
|
raise exception.HBSDBusy(message=msg)
|
|
try:
|
|
paired_info = self.command.get_paired_info(ldev)
|
|
if paired_info['pvol'] is None:
|
|
return
|
|
svol = paired_info['svol'][0]
|
|
if svol['status'] != basic_lib.PSUS:
|
|
msg = basic_lib.output_err(616, pvol=pvol, svol=ldev)
|
|
raise exception.HBSDBusy(message=msg)
|
|
|
|
self.command.delete_pair(pvol, ldev, svol['is_vvol'])
|
|
if not svol['is_vvol']:
|
|
restart = True
|
|
driver.pair_terminate_connection(ldev)
|
|
paired_info = self.command.get_paired_info(pvol)
|
|
if paired_info['pvol'] is None:
|
|
driver.pair_terminate_connection(pvol)
|
|
finally:
|
|
self.volume_info[pvol]['in_use'].lock.release()
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
if restart:
|
|
try:
|
|
self.command.restart_pair_horcm()
|
|
except Exception as e:
|
|
LOG.warning(_LW('Failed to restart horcm: %s'), e)
|
|
else:
|
|
if (all_split or is_vvol) and restart:
|
|
try:
|
|
self.command.restart_pair_horcm()
|
|
except Exception as e:
|
|
LOG.warning(_LW('Failed to restart horcm: %s'), e)
|
|
|
|
def copy_async_data(self, pvol, svol, is_vvol):
|
|
path_list = []
|
|
driver = self.generated_from
|
|
try:
|
|
with self.pair_flock:
|
|
self.delete_pair(pvol, all_split=False, is_vvol=is_vvol)
|
|
paired_info = self.command.get_paired_info(pvol)
|
|
if paired_info['pvol'] is None:
|
|
driver.pair_initialize_connection(pvol)
|
|
path_list.append(pvol)
|
|
driver.pair_initialize_connection(svol)
|
|
path_list.append(svol)
|
|
self.command.comm_create_pair(pvol, svol, is_vvol)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
for ldev in path_list:
|
|
try:
|
|
driver.pair_terminate_connection(ldev)
|
|
except Exception as ex:
|
|
LOG.warning(basic_lib.set_msg(310, ldev=ldev,
|
|
reason=ex))
|
|
|
|
def copy_sync_data(self, src_ldev, dest_ldev, size):
|
|
src_vol = {'provider_location': six.text_type(src_ldev),
|
|
'id': 'src_vol'}
|
|
dest_vol = {'provider_location': six.text_type(dest_ldev),
|
|
'id': 'dest_vol'}
|
|
properties = utils.brick_get_connector_properties()
|
|
driver = self.generated_from
|
|
src_info = None
|
|
dest_info = None
|
|
try:
|
|
dest_info = driver._attach_volume(self.context, dest_vol,
|
|
properties)
|
|
src_info = driver._attach_volume(self.context, src_vol,
|
|
properties)
|
|
volume_utils.copy_volume(src_info['device']['path'],
|
|
dest_info['device']['path'], size * 1024,
|
|
self.configuration.volume_dd_blocksize)
|
|
finally:
|
|
if dest_info:
|
|
driver._detach_volume(self.context, dest_info,
|
|
dest_vol, properties)
|
|
if src_info:
|
|
driver._detach_volume(self.context, src_info,
|
|
src_vol, properties)
|
|
self.command.discard_zero_page(dest_ldev)
|
|
|
|
def copy_data(self, pvol, size, p_is_vvol, method):
|
|
type = 'Normal'
|
|
is_vvol = method == 'THIN'
|
|
svol = self._create_volume(size, is_vvol=is_vvol)
|
|
try:
|
|
if p_is_vvol:
|
|
self.copy_sync_data(pvol, svol, size)
|
|
else:
|
|
if is_vvol:
|
|
type = 'V-VOL'
|
|
self.copy_async_data(pvol, svol, is_vvol)
|
|
except Exception:
|
|
with excutils.save_and_reraise_exception():
|
|
try:
|
|
self.delete_ldev(svol, is_vvol)
|
|
except Exception as ex:
|
|
LOG.warning(basic_lib.set_msg(313, ldev=svol,
|
|
reason=ex))
|
|
|
|
return six.text_type(svol), type
|
|
|
|
def add_lun(self, command, hostgroups, ldev, is_once=False):
|
|
lock = basic_lib.get_process_lock(self.storage_lock_file)
|
|
with lock:
|
|
self.command.comm_add_lun(command, hostgroups, ldev, is_once)
|
|
|
|
def create_ldev(self, size, ldev_range, pool_id, is_vvol):
|
|
LOG.debug('create start (normal)')
|
|
for i in basic_lib.DEFAULT_TRY_RANGE:
|
|
LOG.debug('Try number: %(tries)s / %(max_tries)s',
|
|
{'tries': i + 1,
|
|
'max_tries': len(basic_lib.DEFAULT_TRY_RANGE)})
|
|
new_ldev = self._get_unused_volume_num(ldev_range)
|
|
try:
|
|
self._add_ldev(new_ldev, size, pool_id, is_vvol)
|
|
except exception.HBSDNotFound:
|
|
LOG.warning(basic_lib.set_msg(312, resource='LDEV'))
|
|
continue
|
|
else:
|
|
break
|
|
else:
|
|
msg = basic_lib.output_err(636)
|
|
raise exception.HBSDError(message=msg)
|
|
LOG.debug('create end (normal: %s)', new_ldev)
|
|
self.init_volinfo(self.volume_info, new_ldev)
|
|
return new_ldev
|
|
|
|
def _create_volume(self, size, is_vvol=False):
|
|
ldev_range = self.configuration.hitachi_ldev_range
|
|
if not ldev_range:
|
|
ldev_range = DEFAULT_LDEV_RANGE
|
|
pool_id = self.configuration.hitachi_pool_id
|
|
|
|
lock = basic_lib.get_process_lock(self.storage_lock_file)
|
|
with self.storage_obj_lock, lock:
|
|
ldev = self.create_ldev(size, ldev_range, pool_id, is_vvol)
|
|
return ldev
|
|
|
|
def create_volume(self, volume):
|
|
volume_metadata = self.get_volume_metadata(volume['id'])
|
|
volume_metadata['type'] = 'Normal'
|
|
|
|
size = volume['size']
|
|
ldev = self._create_volume(size)
|
|
volume_metadata['ldev'] = six.text_type(ldev)
|
|
|
|
return {'provider_location': six.text_type(ldev),
|
|
'metadata': volume_metadata}
|
|
|
|
def delete_ldev(self, ldev, is_vvol):
|
|
LOG.debug('Call delete_ldev (LDEV: %(ldev)d is_vvol: %(vvol)s)',
|
|
{'ldev': ldev, 'vvol': is_vvol})
|
|
with self.pair_flock:
|
|
self.delete_pair(ldev)
|
|
self.command.comm_delete_ldev(ldev, is_vvol)
|
|
with self.volinfo_lock:
|
|
if ldev in self.volume_info:
|
|
self.volume_info.pop(ldev)
|
|
LOG.debug('delete_ldev is finished '
|
|
'(LDEV: %(ldev)d, is_vvol: %(vvol)s)',
|
|
{'ldev': ldev, 'vvol': is_vvol})
|
|
|
|
def delete_volume(self, volume):
|
|
ldev = self.get_ldev(volume)
|
|
if ldev is None:
|
|
LOG.warning(basic_lib.set_msg(304, method='delete_volume',
|
|
id=volume['id']))
|
|
return
|
|
self.add_volinfo(ldev, volume['id'])
|
|
if not self.volume_info[ldev]['in_use'].lock.acquire(False):
|
|
desc = self.volume_info[ldev]['in_use'].desc
|
|
basic_lib.output_err(660, desc=desc)
|
|
raise exception.VolumeIsBusy(volume_name=volume['name'])
|
|
try:
|
|
is_vvol = self.get_volume_is_vvol(volume)
|
|
try:
|
|
self.delete_ldev(ldev, is_vvol)
|
|
except exception.HBSDNotFound:
|
|
with self.volinfo_lock:
|
|
if ldev in self.volume_info:
|
|
self.volume_info.pop(ldev)
|
|
LOG.warning(basic_lib.set_msg(
|
|
305, type='volume', id=volume['id']))
|
|
except exception.HBSDBusy:
|
|
raise exception.VolumeIsBusy(volume_name=volume['name'])
|
|
finally:
|
|
if ldev in self.volume_info:
|
|
self.volume_info[ldev]['in_use'].lock.release()
|
|
|
|
def check_volume_status(self, volume, is_vvol):
|
|
if not is_vvol:
|
|
status = VALID_DP_VOLUME_STATUS
|
|
else:
|
|
status = VALID_V_VOLUME_STATUS
|
|
if volume['status'] not in status:
|
|
msg = basic_lib.output_err(654, status=volume['status'])
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
def create_snapshot(self, snapshot):
|
|
src_ref = self.get_volume(snapshot['volume_id'])
|
|
pvol = self.get_ldev(src_ref)
|
|
if pvol is None:
|
|
msg = basic_lib.output_err(624, type='volume', id=src_ref['id'])
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
self.add_volinfo(pvol, src_ref['id'])
|
|
with self.volume_info[pvol]['in_use']:
|
|
is_vvol = self.get_volume_is_vvol(src_ref)
|
|
self.check_volume_status(src_ref, is_vvol)
|
|
size = snapshot['volume_size']
|
|
snap_metadata = snapshot.get('metadata')
|
|
method = None if is_vvol else self.get_copy_method(src_ref)
|
|
|
|
svol, type = self.copy_data(pvol, size, is_vvol, method)
|
|
|
|
if type == 'V-VOL':
|
|
snap_metadata['type'] = type
|
|
snap_metadata['ldev'] = svol
|
|
|
|
return {'provider_location': svol,
|
|
'metadata': snap_metadata}
|
|
|
|
def delete_snapshot(self, snapshot):
|
|
ldev = self.get_ldev(snapshot)
|
|
if ldev is None:
|
|
LOG.warning(basic_lib.set_msg(
|
|
304, method='delete_snapshot', id=snapshot['id']))
|
|
return
|
|
self.add_volinfo(ldev, id=snapshot['id'], type='snapshot')
|
|
if not self.volume_info[ldev]['in_use'].lock.acquire(False):
|
|
desc = self.volume_info[ldev]['in_use'].desc
|
|
basic_lib.output_err(660, desc=desc)
|
|
raise exception.SnapshotIsBusy(snapshot_name=snapshot['name'])
|
|
try:
|
|
is_vvol = self.get_snapshot_is_vvol(snapshot)
|
|
try:
|
|
self.delete_ldev(ldev, is_vvol)
|
|
except exception.HBSDNotFound:
|
|
with self.volinfo_lock:
|
|
if ldev in self.volume_info:
|
|
self.volume_info.pop(ldev)
|
|
LOG.warning(basic_lib.set_msg(
|
|
305, type='snapshot', id=snapshot['id']))
|
|
except exception.HBSDBusy:
|
|
raise exception.SnapshotIsBusy(snapshot_name=snapshot['name'])
|
|
finally:
|
|
if ldev in self.volume_info:
|
|
self.volume_info[ldev]['in_use'].lock.release()
|
|
|
|
def create_cloned_volume(self, volume, src_vref):
|
|
pvol = self.get_ldev(src_vref)
|
|
if pvol is None:
|
|
msg = basic_lib.output_err(624, type='volume', id=src_vref['id'])
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
self.add_volinfo(pvol, src_vref['id'])
|
|
with self.volume_info[pvol]['in_use']:
|
|
is_vvol = self.get_volume_is_vvol(src_vref)
|
|
self.check_volume_status(self.get_volume(src_vref['id']), is_vvol)
|
|
size = volume['size']
|
|
src_size = src_vref['size']
|
|
if size < src_size:
|
|
msg = basic_lib.output_err(617, type='volume',
|
|
volume_id=volume['id'])
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
metadata = self.get_volume_metadata(volume['id'])
|
|
method = None if is_vvol else self.get_copy_method(volume)
|
|
|
|
svol, type = self.copy_data(pvol, src_size, is_vvol, method)
|
|
|
|
if size > src_size:
|
|
self.extend_volume(volume, size)
|
|
|
|
metadata['type'] = type
|
|
metadata['volume'] = src_vref['id']
|
|
metadata['ldev'] = svol
|
|
|
|
return {'provider_location': svol, 'metadata': metadata}
|
|
|
|
def create_volume_from_snapshot(self, volume, snapshot):
|
|
pvol = self.get_ldev(snapshot)
|
|
if pvol is None:
|
|
msg = basic_lib.output_err(624, type='snapshot', id=snapshot['id'])
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
self.add_volinfo(pvol, id=snapshot['id'], type='snapshot')
|
|
with self.volume_info[pvol]['in_use']:
|
|
is_vvol = self.get_snapshot_is_vvol(snapshot)
|
|
if snapshot['status'] != 'available':
|
|
msg = basic_lib.output_err(655, status=snapshot['status'])
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
size = volume['size']
|
|
src_size = snapshot['volume_size']
|
|
if size != src_size:
|
|
msg = basic_lib.output_err(617, type='snapshot',
|
|
volume_id=volume['id'])
|
|
raise exception.HBSDError(message=msg)
|
|
|
|
metadata = self.get_volume_metadata(volume['id'])
|
|
method = None if is_vvol else self.get_copy_method(volume)
|
|
svol, type = self.copy_data(pvol, size, is_vvol, method)
|
|
|
|
metadata['type'] = type
|
|
metadata['snapshot'] = snapshot['id']
|
|
metadata['ldev'] = svol
|
|
|
|
return {'provider_location': svol, 'metadata': metadata}
|
|
|
|
def _extend_volume(self, ldev, old_size, new_size):
|
|
with self.pair_flock:
|
|
self.delete_pair(ldev)
|
|
self.command.comm_extend_ldev(ldev, old_size, new_size)
|
|
|
|
def extend_volume(self, volume, new_size):
|
|
pvol = self.get_ldev(volume)
|
|
self.add_volinfo(pvol, volume['id'])
|
|
with self.volume_info[pvol]['in_use']:
|
|
if self.get_volume_is_vvol(volume):
|
|
msg = basic_lib.output_err(618, volume_id=volume['id'])
|
|
raise exception.HBSDError(message=msg)
|
|
self._extend_volume(pvol, volume['size'], new_size)
|
|
|
|
def output_backend_available_once(self):
|
|
if self.output_first:
|
|
self.output_first = False
|
|
LOG.warning(basic_lib.set_msg(
|
|
3, config_group=self.configuration.config_group))
|
|
|
|
def update_volume_stats(self, storage_protocol):
|
|
data = {}
|
|
total_gb = None
|
|
free_gb = None
|
|
data['volume_backend_name'] = self.configuration.safe_get(
|
|
'volume_backend_name') or 'HBSD%s' % storage_protocol
|
|
data['vendor_name'] = 'Hitachi'
|
|
data['driver_version'] = VERSION
|
|
data['storage_protocol'] = storage_protocol
|
|
|
|
try:
|
|
total_gb, free_gb = self.command.comm_get_dp_pool(
|
|
self.configuration.hitachi_pool_id)
|
|
except Exception as ex:
|
|
LOG.error(_LE('Failed to update volume status: %s'), ex)
|
|
return None
|
|
|
|
data['total_capacity_gb'] = total_gb
|
|
data['free_capacity_gb'] = free_gb
|
|
data['reserved_percentage'] = self.configuration.safe_get(
|
|
'reserved_percentage')
|
|
data['QoS_support'] = False
|
|
|
|
LOG.debug('Updating volume status (%s)', data)
|
|
|
|
return data
|
|
|
|
def init_volinfo(self, vol_info, ldev):
|
|
vol_info[ldev] = {'in_use': TryLock(), 'lock': threading.Lock()}
|
|
|
|
def manage_existing(self, volume, existing_ref):
|
|
"""Manage an existing Hitachi storage volume.
|
|
|
|
existing_ref is a dictionary of the form:
|
|
|
|
For HUS 100 Family,
|
|
{'ldev': <logical device number on storage>,
|
|
'unit_name': <storage device name>}
|
|
|
|
For VSP G1000/VSP/HUS VM,
|
|
{'ldev': <logical device number on storage>,
|
|
'serial_number': <product number of storage system>}
|
|
"""
|
|
|
|
ldev = self._string2int(existing_ref.get('ldev'))
|
|
|
|
LOG.info(basic_lib.set_msg(4, volume_id=volume['id'], ldev=ldev))
|
|
|
|
return {'provider_location': ldev}
|
|
|
|
def _manage_existing_get_size(self, volume, existing_ref):
|
|
"""Return size of volume for manage_existing."""
|
|
|
|
ldev = self._string2int(existing_ref.get('ldev'))
|
|
if ldev is None:
|
|
msg = basic_lib.output_err(701)
|
|
raise exception.HBSDError(data=msg)
|
|
|
|
size = self.command.get_ldev_size_in_gigabyte(ldev, existing_ref)
|
|
|
|
metadata = {'type': basic_lib.NORMAL_VOLUME_TYPE, 'ldev': ldev}
|
|
self._update_volume_metadata(volume['id'], metadata)
|
|
|
|
return size
|
|
|
|
def manage_existing_get_size(self, volume, existing_ref):
|
|
try:
|
|
return self._manage_existing_get_size(volume, existing_ref)
|
|
except exception.HBSDError as ex:
|
|
raise exception.ManageExistingInvalidReference(
|
|
existing_ref=existing_ref,
|
|
reason=six.text_type(ex))
|
|
|
|
def _unmanage(self, volume, ldev):
|
|
with self.horcmgr_flock:
|
|
self.delete_pair(ldev)
|
|
|
|
with self.volinfo_lock:
|
|
if ldev in self.volume_info:
|
|
self.volume_info.pop(ldev)
|
|
|
|
def unmanage(self, volume):
|
|
"""Remove the specified volume from Cinder management."""
|
|
|
|
ldev = self.get_ldev(volume)
|
|
|
|
if ldev is None:
|
|
return
|
|
|
|
self.add_volinfo(ldev, volume['id'])
|
|
if not self.volume_info[ldev]['in_use'].lock.acquire(False):
|
|
desc = self.volume_info[ldev]['in_use'].desc
|
|
basic_lib.output_err(660, desc=desc)
|
|
raise exception.HBSDVolumeIsBusy(volume_name=volume['name'])
|
|
|
|
is_vvol = self.get_volume_is_vvol(volume)
|
|
if is_vvol:
|
|
basic_lib.output_err(706, volume_id=volume['id'],
|
|
volume_type=basic_lib.NORMAL_VOLUME_TYPE)
|
|
raise exception.HBSDVolumeIsBusy(volume_name=volume['name'])
|
|
try:
|
|
self._unmanage(volume, ldev)
|
|
except exception.HBSDBusy:
|
|
raise exception.HBSDVolumeIsBusy(volume_name=volume['name'])
|
|
else:
|
|
LOG.info(basic_lib.set_msg(5, volume_id=volume['id'], ldev=ldev))
|
|
finally:
|
|
if ldev in self.volume_info:
|
|
self.volume_info[ldev]['in_use'].lock.release()
|