Transition LVM Driver to use Target Objects
This patch refactors the LVM Driver to take a seperate Target object instead of mixing the control and data path implementations inside the driver itself. It removes the volume/iscsi.py and brick/iscsis/* files which were duplicating code and actually very messy in terms of where calls were actually being implemented. Change-Id: I43190d1dac33748fe55fa00f260f32ab209be656
This commit is contained in:
parent
1283b4c6a8
commit
9651f54714
|
@ -1,648 +0,0 @@
|
|||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
"""
|
||||
Helper code for the iSCSI volume driver.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import time
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo_concurrency import processutils as putils
|
||||
import six
|
||||
|
||||
from cinder.brick import exception
|
||||
from cinder.brick import executor
|
||||
from cinder.i18n import _, _LE, _LI, _LW
|
||||
from cinder.openstack.common import fileutils
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TargetAdmin(executor.Executor):
|
||||
"""iSCSI target administration.
|
||||
|
||||
Base class for iSCSI target admin helpers.
|
||||
"""
|
||||
|
||||
def __init__(self, cmd, root_helper, execute):
|
||||
super(TargetAdmin, self).__init__(root_helper, execute=execute)
|
||||
|
||||
# NOTE(jdg): cmd is a prefix to the target helper utility we
|
||||
# use. This can be tgtadm, cinder-rtstool etc
|
||||
self._cmd = cmd
|
||||
|
||||
def _run(self, cmd, *args, **kwargs):
|
||||
return self._execute(cmd,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
def _get_target_chap_auth(self, volume_id):
|
||||
"""Get the current chap auth username and password."""
|
||||
return None
|
||||
|
||||
def create_iscsi_target(self, name, tid, lun, path,
|
||||
chap_auth=None, **kwargs):
|
||||
"""Create an iSCSI target and logical unit."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def remove_iscsi_target(self, tid, lun, vol_id, vol_name, **kwargs):
|
||||
"""Remove an iSCSI target and logical unit."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _new_target(self, name, tid, **kwargs):
|
||||
"""Create a new iSCSI target."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _delete_target(self, tid, **kwargs):
|
||||
"""Delete a target."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def show_target(self, tid, iqn=None, **kwargs):
|
||||
"""Query the given target ID."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _new_logicalunit(self, tid, lun, path, **kwargs):
|
||||
"""Create a new LUN on a target using the supplied path."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _delete_logicalunit(self, tid, lun, **kwargs):
|
||||
"""Delete a logical unit from a target."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class TgtAdm(TargetAdmin):
|
||||
"""iSCSI target administration using tgtadm."""
|
||||
VOLUME_CONF = """
|
||||
<target %s>
|
||||
backing-store %s
|
||||
driver iscsi
|
||||
write-cache %s
|
||||
</target>
|
||||
"""
|
||||
VOLUME_CONF_WITH_CHAP_AUTH = """
|
||||
<target %s>
|
||||
backing-store %s
|
||||
driver iscsi
|
||||
%s
|
||||
write-cache %s
|
||||
</target>
|
||||
"""
|
||||
|
||||
def __init__(self, root_helper, volumes_dir,
|
||||
target_prefix='iqn.2010-10.org.openstack:',
|
||||
execute=putils.execute):
|
||||
super(TgtAdm, self).__init__('tgtadm', root_helper, execute)
|
||||
|
||||
self.iscsi_target_prefix = target_prefix
|
||||
self.volumes_dir = volumes_dir
|
||||
|
||||
def _get_target(self, iqn):
|
||||
(out, _err) = self._run('tgt-admin', '--show', run_as_root=True)
|
||||
lines = out.split('\n')
|
||||
for line in lines:
|
||||
if iqn in line:
|
||||
parsed = line.split()
|
||||
tid = parsed[1]
|
||||
return tid[:-1]
|
||||
|
||||
return None
|
||||
|
||||
def _verify_backing_lun(self, iqn, tid):
|
||||
backing_lun = True
|
||||
capture = False
|
||||
target_info = []
|
||||
|
||||
(out, _err) = self._run('tgt-admin', '--show', run_as_root=True)
|
||||
lines = out.split('\n')
|
||||
|
||||
for line in lines:
|
||||
if iqn in line and "Target %s" % tid in line:
|
||||
capture = True
|
||||
if capture:
|
||||
target_info.append(line)
|
||||
if iqn not in line and 'Target ' in line:
|
||||
capture = False
|
||||
|
||||
if ' LUN: 1' not in target_info:
|
||||
backing_lun = False
|
||||
|
||||
return backing_lun
|
||||
|
||||
def _recreate_backing_lun(self, iqn, tid, name, path):
|
||||
LOG.warning(_LW('Attempting recreate of backing lun...'))
|
||||
|
||||
# Since we think the most common case of this is a dev busy
|
||||
# (create vol from snapshot) we're going to add a sleep here
|
||||
# this will hopefully give things enough time to stabilize
|
||||
# how long should we wait?? I have no idea, let's go big
|
||||
# and error on the side of caution
|
||||
|
||||
time.sleep(10)
|
||||
try:
|
||||
(out, err) = self._run('tgtadm', '--lld', 'iscsi',
|
||||
'--op', 'new', '--mode',
|
||||
'logicalunit', '--tid',
|
||||
tid, '--lun', '1', '-b',
|
||||
path, run_as_root=True)
|
||||
except putils.ProcessExecutionError as e:
|
||||
LOG.error(_LE("Failed to recover attempt to create "
|
||||
"iscsi backing lun for Volume "
|
||||
"ID: %(vol_id)s: %(e)s"),
|
||||
{'vol_id': name, 'e': e})
|
||||
|
||||
def _get_target_chap_auth(self, name):
|
||||
volumes_dir = self.volumes_dir
|
||||
vol_id = name.split(':')[1]
|
||||
volume_path = os.path.join(volumes_dir, vol_id)
|
||||
|
||||
try:
|
||||
with open(volume_path, 'r') as f:
|
||||
volume_conf = f.read()
|
||||
except Exception as e:
|
||||
# NOTE(jdg): Debug is ok here because the caller
|
||||
# will just generate the CHAP creds and create the
|
||||
# file based on the None return
|
||||
LOG.debug('Failed to open config for %(vol_id)s: %(e)s',
|
||||
{'vol_id': vol_id, 'e': six.text_type(e)})
|
||||
return None
|
||||
|
||||
m = re.search('incominguser (\w+) (\w+)', volume_conf)
|
||||
if m:
|
||||
return (m.group(1), m.group(2))
|
||||
LOG.debug('Failed to find CHAP auth from config for %s', vol_id)
|
||||
return None
|
||||
|
||||
def create_iscsi_target(self, name, tid, lun, path,
|
||||
chap_auth=None, **kwargs):
|
||||
# Note(jdg) tid and lun aren't used by TgtAdm but remain for
|
||||
# compatibility
|
||||
|
||||
fileutils.ensure_tree(self.volumes_dir)
|
||||
|
||||
vol_id = name.split(':')[1]
|
||||
write_cache = kwargs.get('write_cache', 'on')
|
||||
if chap_auth is None:
|
||||
volume_conf = self.VOLUME_CONF % (name, path, write_cache)
|
||||
else:
|
||||
chap_str = re.sub('^IncomingUser ', 'incominguser ', chap_auth)
|
||||
volume_conf = self.VOLUME_CONF_WITH_CHAP_AUTH % (name,
|
||||
path, chap_str,
|
||||
write_cache)
|
||||
|
||||
LOG.info(_LI('Creating iscsi_target for: %s'), vol_id)
|
||||
volumes_dir = self.volumes_dir
|
||||
volume_path = os.path.join(volumes_dir, vol_id)
|
||||
|
||||
f = open(volume_path, 'w+')
|
||||
f.write(volume_conf)
|
||||
f.close()
|
||||
LOG.debug('Created volume path %(vp)s,\n'
|
||||
'content: %(vc)s',
|
||||
{'vp': volume_path, 'vc': volume_conf})
|
||||
|
||||
old_persist_file = None
|
||||
old_name = kwargs.get('old_name', None)
|
||||
if old_name is not None:
|
||||
old_persist_file = os.path.join(volumes_dir, old_name)
|
||||
|
||||
try:
|
||||
# with the persistent tgts we create them
|
||||
# by creating the entry in the persist file
|
||||
# and then doing an update to get the target
|
||||
# created.
|
||||
(out, err) = self._run('tgt-admin', '--update', name,
|
||||
run_as_root=True)
|
||||
|
||||
# Grab targets list for debug
|
||||
# Consider adding a check for lun 0 and 1 for tgtadm
|
||||
# before considering this as valid
|
||||
(out, err) = self._run('tgtadm',
|
||||
'--lld',
|
||||
'iscsi',
|
||||
'--op',
|
||||
'show',
|
||||
'--mode',
|
||||
'target',
|
||||
run_as_root=True)
|
||||
LOG.debug("Targets after update: %s", out)
|
||||
except putils.ProcessExecutionError as e:
|
||||
LOG.warning(_LW("Failed to create iscsi target for volume "
|
||||
"id:%(vol_id)s: %(e)s"),
|
||||
{'vol_id': vol_id, 'e': e.stderr})
|
||||
if "target already exists" in e.stderr:
|
||||
|
||||
LOG.warning(_LW('Create iscsi target failed for '
|
||||
'target already exists'))
|
||||
# NOTE(jdg): We've run into some cases where the cmd being
|
||||
# sent was not correct. May be related to using the
|
||||
# executor direclty?
|
||||
# Adding the additional Warning message above to provide
|
||||
# a very cleary marker for ER, and if the tgt exists let's
|
||||
# just try and use it and move along.
|
||||
# Ref bug: #1398078
|
||||
pass
|
||||
else:
|
||||
# Don't forget to remove the persistent file we created
|
||||
os.unlink(volume_path)
|
||||
raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
|
||||
|
||||
iqn = '%s%s' % (self.iscsi_target_prefix, vol_id)
|
||||
tid = self._get_target(iqn)
|
||||
if tid is None:
|
||||
LOG.error(_LE("Failed to create iscsi target for Volume "
|
||||
"ID: %(vol_id)s. Ensure the tgtd config file "
|
||||
"contains 'include %(volumes_dir)s/*'"), {
|
||||
'vol_id': vol_id, 'volumes_dir': volumes_dir, })
|
||||
raise exception.NotFound()
|
||||
|
||||
# NOTE(jdg): Sometimes we have some issues with the backing lun
|
||||
# not being created, believe this is due to a device busy
|
||||
# or something related, so we're going to add some code
|
||||
# here that verifies the backing lun (lun 1) was created
|
||||
# and we'll try and recreate it if it's not there
|
||||
if not self._verify_backing_lun(iqn, tid):
|
||||
try:
|
||||
self._recreate_backing_lun(iqn, tid, name, path)
|
||||
except putils.ProcessExecutionError:
|
||||
os.unlink(volume_path)
|
||||
raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
|
||||
|
||||
# Finally check once more and if no go, fail and punt
|
||||
if not self._verify_backing_lun(iqn, tid):
|
||||
os.unlink(volume_path)
|
||||
raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
|
||||
|
||||
if old_persist_file is not None and os.path.exists(old_persist_file):
|
||||
os.unlink(old_persist_file)
|
||||
|
||||
return tid
|
||||
|
||||
def remove_iscsi_target(self, tid, lun, vol_id, vol_name, **kwargs):
|
||||
LOG.info(_LI('Removing iscsi_target for: %s'), vol_id)
|
||||
vol_uuid_file = vol_name
|
||||
volume_path = os.path.join(self.volumes_dir, vol_uuid_file)
|
||||
if not os.path.exists(volume_path):
|
||||
LOG.warning(_LW('Volume path %s does not exist, '
|
||||
'nothing to remove.'), volume_path)
|
||||
return
|
||||
|
||||
if os.path.isfile(volume_path):
|
||||
iqn = '%s%s' % (self.iscsi_target_prefix,
|
||||
vol_uuid_file)
|
||||
else:
|
||||
raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
|
||||
try:
|
||||
# NOTE(vish): --force is a workaround for bug:
|
||||
# https://bugs.launchpad.net/cinder/+bug/1159948
|
||||
self._run('tgt-admin',
|
||||
'--force',
|
||||
'--delete',
|
||||
iqn,
|
||||
run_as_root=True,
|
||||
attempts=CONF.num_shell_tries)
|
||||
except putils.ProcessExecutionError as e:
|
||||
LOG.error(_LE("Failed to remove iscsi target for Volume "
|
||||
"ID: %(vol_id)s: %(e)s"),
|
||||
{'vol_id': vol_id, 'e': e})
|
||||
raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
|
||||
|
||||
# NOTE(jdg): There's a bug in some versions of tgt that
|
||||
# will sometimes fail silently when using the force flag
|
||||
# https://bugs.launchpad.net/ubuntu/+source/tgt/+bug/1305343
|
||||
# For now work-around by checking if the target was deleted,
|
||||
# if it wasn't, try again without the force.
|
||||
|
||||
# This will NOT do any good for the case of multiple sessions
|
||||
# which the force was aded for but it will however address
|
||||
# the cases pointed out in bug:
|
||||
# https://bugs.launchpad.net/cinder/+bug/1304122
|
||||
if self._get_target(iqn):
|
||||
try:
|
||||
LOG.warning(_LW('Silent failure of target removal '
|
||||
'detected, retry....'))
|
||||
self._run('tgt-admin',
|
||||
'--delete',
|
||||
iqn,
|
||||
run_as_root=True)
|
||||
except putils.ProcessExecutionError as e:
|
||||
LOG.error(_LE("Failed to remove iscsi target for Volume "
|
||||
"ID: %(vol_id)s: %(e)s"),
|
||||
{'vol_id': vol_id, 'e': e})
|
||||
raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
|
||||
|
||||
# NOTE(jdg): This *should* be there still but incase
|
||||
# it's not we don't care, so just ignore it if was
|
||||
# somehow deleted between entry of this method
|
||||
# and here
|
||||
if os.path.exists(volume_path):
|
||||
os.unlink(volume_path)
|
||||
else:
|
||||
LOG.debug('Volume path %s not found at end, '
|
||||
'of remove_iscsi_target.', volume_path)
|
||||
|
||||
def show_target(self, tid, iqn=None, **kwargs):
|
||||
if iqn is None:
|
||||
raise exception.InvalidParameterValue(
|
||||
err=_('valid iqn needed for show_target'))
|
||||
|
||||
tid = self._get_target(iqn)
|
||||
if tid is None:
|
||||
raise exception.NotFound()
|
||||
|
||||
|
||||
class IetAdm(TargetAdmin):
|
||||
"""iSCSI target administration using ietadm."""
|
||||
|
||||
def __init__(self, root_helper, iet_conf='/etc/iet/ietd.conf',
|
||||
iscsi_iotype='fileio', execute=putils.execute):
|
||||
super(IetAdm, self).__init__('ietadm', root_helper, execute)
|
||||
self.iet_conf = iet_conf
|
||||
self.iscsi_iotype = iscsi_iotype
|
||||
|
||||
def _is_block(self, path):
|
||||
mode = os.stat(path).st_mode
|
||||
return stat.S_ISBLK(mode)
|
||||
|
||||
def _iotype(self, path):
|
||||
if self.iscsi_iotype == 'auto':
|
||||
return 'blockio' if self._is_block(path) else 'fileio'
|
||||
else:
|
||||
return self.iscsi_iotype
|
||||
|
||||
def create_iscsi_target(self, name, tid, lun, path,
|
||||
chap_auth=None, **kwargs):
|
||||
|
||||
# NOTE (jdg): Address bug: 1175207
|
||||
kwargs.pop('old_name', None)
|
||||
|
||||
self._new_target(name, tid, **kwargs)
|
||||
self._new_logicalunit(tid, lun, path, **kwargs)
|
||||
if chap_auth is not None:
|
||||
(type, username, password) = chap_auth.split()
|
||||
self._new_auth(tid, type, username, password, **kwargs)
|
||||
|
||||
conf_file = self.iet_conf
|
||||
if os.path.exists(conf_file):
|
||||
try:
|
||||
volume_conf = """
|
||||
Target %s
|
||||
%s
|
||||
Lun 0 Path=%s,Type=%s
|
||||
""" % (name, chap_auth, path, self._iotype(path))
|
||||
|
||||
with utils.temporary_chown(conf_file):
|
||||
f = open(conf_file, 'a+')
|
||||
f.write(volume_conf)
|
||||
f.close()
|
||||
except putils.ProcessExecutionError as e:
|
||||
vol_id = name.split(':')[1]
|
||||
LOG.error(_LE("Failed to create iscsi target for Volume "
|
||||
"ID: %(vol_id)s: %(e)s"),
|
||||
{'vol_id': vol_id, 'e': e})
|
||||
raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
|
||||
return tid
|
||||
|
||||
def remove_iscsi_target(self, tid, lun, vol_id, vol_name, **kwargs):
|
||||
LOG.info(_LI('Removing iscsi_target for volume: %s'), vol_id)
|
||||
self._delete_logicalunit(tid, lun, **kwargs)
|
||||
self._delete_target(tid, **kwargs)
|
||||
vol_uuid_file = vol_name
|
||||
conf_file = self.iet_conf
|
||||
if os.path.exists(conf_file):
|
||||
with utils.temporary_chown(conf_file):
|
||||
try:
|
||||
iet_conf_text = open(conf_file, 'r+')
|
||||
full_txt = iet_conf_text.readlines()
|
||||
new_iet_conf_txt = []
|
||||
count = 0
|
||||
for line in full_txt:
|
||||
if count > 0:
|
||||
count -= 1
|
||||
continue
|
||||
elif re.search(vol_uuid_file, line):
|
||||
count = 2
|
||||
continue
|
||||
else:
|
||||
new_iet_conf_txt.append(line)
|
||||
|
||||
iet_conf_text.seek(0)
|
||||
iet_conf_text.truncate(0)
|
||||
iet_conf_text.writelines(new_iet_conf_txt)
|
||||
finally:
|
||||
iet_conf_text.close()
|
||||
|
||||
def _new_target(self, name, tid, **kwargs):
|
||||
self._run(self._cmd, '--op', 'new',
|
||||
'--tid=%s' % tid,
|
||||
'--params', 'Name=%s' % name,
|
||||
**kwargs)
|
||||
|
||||
def _delete_target(self, tid, **kwargs):
|
||||
self._run(self._cmd, '--op', 'delete',
|
||||
'--tid=%s' % tid,
|
||||
**kwargs)
|
||||
|
||||
def show_target(self, tid, iqn=None, **kwargs):
|
||||
self._run(self._cmd, '--op', 'show',
|
||||
'--tid=%s' % tid,
|
||||
**kwargs)
|
||||
|
||||
def _new_logicalunit(self, tid, lun, path, **kwargs):
|
||||
self._run(self._cmd, '--op', 'new',
|
||||
'--tid=%s' % tid,
|
||||
'--lun=%d' % lun,
|
||||
'--params', 'Path=%s,Type=%s' % (path, self._iotype(path)),
|
||||
**kwargs)
|
||||
|
||||
def _delete_logicalunit(self, tid, lun, **kwargs):
|
||||
self._run(self._cmd, '--op', 'delete',
|
||||
'--tid=%s' % tid,
|
||||
'--lun=%d' % lun,
|
||||
**kwargs)
|
||||
|
||||
def _new_auth(self, tid, type, username, password, **kwargs):
|
||||
self._run(self._cmd, '--op', 'new',
|
||||
'--tid=%s' % tid,
|
||||
'--user',
|
||||
'--params=%s=%s,Password=%s' % (type, username, password),
|
||||
**kwargs)
|
||||
|
||||
|
||||
class FakeIscsiHelper(object):
|
||||
|
||||
def __init__(self):
|
||||
self.tid = 1
|
||||
self._execute = None
|
||||
|
||||
def set_execute(self, execute):
|
||||
self._execute = execute
|
||||
|
||||
def create_iscsi_target(self, *args, **kwargs):
|
||||
self.tid += 1
|
||||
return self.tid
|
||||
|
||||
|
||||
class LioAdm(TargetAdmin):
|
||||
"""iSCSI target administration for LIO using python-rtslib."""
|
||||
def __init__(self, root_helper,
|
||||
iscsi_target_prefix='iqn.2010-10.org.openstack:',
|
||||
execute=putils.execute):
|
||||
super(LioAdm, self).__init__('cinder-rtstool', root_helper, execute)
|
||||
|
||||
self.iscsi_target_prefix = iscsi_target_prefix
|
||||
self._verify_rtstool()
|
||||
|
||||
def _verify_rtstool(self):
|
||||
try:
|
||||
self._run('cinder-rtstool', 'verify')
|
||||
except (OSError, putils.ProcessExecutionError):
|
||||
LOG.error(_LE('cinder-rtstool is not installed correctly'))
|
||||
raise
|
||||
|
||||
def _get_target(self, iqn):
|
||||
(out, _err) = self._run('cinder-rtstool',
|
||||
'get-targets',
|
||||
run_as_root=True)
|
||||
lines = out.split('\n')
|
||||
for line in lines:
|
||||
if iqn in line:
|
||||
return line
|
||||
|
||||
return None
|
||||
|
||||
def create_iscsi_target(self, name, tid, lun, path,
|
||||
chap_auth=None, **kwargs):
|
||||
# tid and lun are not used
|
||||
|
||||
vol_id = name.split(':')[1]
|
||||
|
||||
LOG.info(_LI('Creating iscsi_target for volume: %s'), vol_id)
|
||||
|
||||
if chap_auth is not None:
|
||||
(chap_auth_userid, chap_auth_password) = chap_auth.split(' ')[1:]
|
||||
|
||||
try:
|
||||
command_args = ['create',
|
||||
path,
|
||||
name,
|
||||
chap_auth_userid,
|
||||
chap_auth_password]
|
||||
self._run('cinder-rtstool', *command_args, run_as_root=True)
|
||||
except putils.ProcessExecutionError as e:
|
||||
LOG.error(_LE("Failed to create iscsi target for Volume "
|
||||
"ID: %(vol_id)s, Error: %(err)s."),
|
||||
{'vol_id': vol_id, 'err': e.stderr})
|
||||
|
||||
raise exception.ISCSITargetCreateFailed(volume_id=vol_id)
|
||||
|
||||
iqn = '%s%s' % (self.iscsi_target_prefix, vol_id)
|
||||
tid = self._get_target(iqn)
|
||||
if tid is None:
|
||||
LOG.error(_LE("Failed to create iscsi target for Volume "
|
||||
"ID: %s."), vol_id)
|
||||
raise exception.NotFound()
|
||||
|
||||
return tid
|
||||
|
||||
def remove_iscsi_target(self, tid, lun, vol_id, vol_name, **kwargs):
|
||||
LOG.info(_LI('Removing iscsi_target: %s'), vol_id)
|
||||
vol_uuid_name = vol_name
|
||||
iqn = '%s%s' % (self.iscsi_target_prefix, vol_uuid_name)
|
||||
|
||||
try:
|
||||
self._run('cinder-rtstool',
|
||||
'delete',
|
||||
iqn,
|
||||
run_as_root=True)
|
||||
except putils.ProcessExecutionError as e:
|
||||
LOG.error(_LE("Failed to remove iscsi target for Volume "
|
||||
"ID: %(vol_id)s, Error: %(err)s."),
|
||||
{'vol_id': vol_id, 'err': e.stderr})
|
||||
raise exception.ISCSITargetRemoveFailed(volume_id=vol_id)
|
||||
|
||||
def show_target(self, tid, iqn=None, **kwargs):
|
||||
if iqn is None:
|
||||
raise exception.InvalidParameterValue(
|
||||
err=_('valid iqn needed for show_target'))
|
||||
|
||||
tid = self._get_target(iqn)
|
||||
if tid is None:
|
||||
raise exception.NotFound()
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
volume_iqn = volume['provider_location'].split(' ')[1]
|
||||
|
||||
(_auth_method, auth_user, auth_pass) = \
|
||||
volume['provider_auth'].split(' ', 3)
|
||||
|
||||
# Add initiator iqns to target ACL
|
||||
try:
|
||||
self._run('cinder-rtstool', 'add-initiator',
|
||||
volume_iqn,
|
||||
auth_user,
|
||||
auth_pass,
|
||||
connector['initiator'],
|
||||
run_as_root=True)
|
||||
except putils.ProcessExecutionError:
|
||||
LOG.error(_LE("Failed to add initiator iqn %s to target."),
|
||||
connector['initiator'])
|
||||
raise exception.ISCSITargetAttachFailed(volume_id=volume['id'])
|
||||
|
||||
def terminate_connection(self, volume, connector):
|
||||
volume_iqn = volume['provider_location'].split(' ')[1]
|
||||
|
||||
# Delete initiator iqns from target ACL
|
||||
try:
|
||||
self._run('cinder-rtstool', 'delete-initiator',
|
||||
volume_iqn,
|
||||
connector['initiator'],
|
||||
run_as_root=True)
|
||||
except putils.ProcessExecutionError:
|
||||
LOG.error(_LE("Failed to delete initiator iqn %s to target."),
|
||||
connector['initiator'])
|
||||
raise exception.ISCSITargetAttachFailed(volume_id=volume['id'])
|
||||
|
||||
|
||||
class ISERTgtAdm(TgtAdm):
|
||||
VOLUME_CONF = """
|
||||
<target %s>
|
||||
driver iser
|
||||
backing-store %s
|
||||
write_cache %s
|
||||
</target>
|
||||
"""
|
||||
VOLUME_CONF_WITH_CHAP_AUTH = """
|
||||
<target %s>
|
||||
driver iser
|
||||
backing-store %s
|
||||
%s
|
||||
write_cache %s
|
||||
</target>
|
||||
"""
|
||||
|
||||
def __init__(self, root_helper, volumes_dir,
|
||||
target_prefix='iqn.2010-10.org.iser.openstack:',
|
||||
execute=putils.execute):
|
||||
super(ISERTgtAdm, self).__init__(root_helper, volumes_dir,
|
||||
target_prefix, execute)
|
|
@ -31,6 +31,7 @@ from cinder.tests.api import fakes
|
|||
from cinder.tests.api.v2 import stubs
|
||||
from cinder.tests import cast_as_call
|
||||
from cinder.volume import api as volume_api
|
||||
from cinder.volume.targets import tgt
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
@ -59,6 +60,13 @@ class AdminActionsTest(test.TestCase):
|
|||
cast_as_call.mock_cast_as_call(self.volume_api.volume_rpcapi.client)
|
||||
cast_as_call.mock_cast_as_call(self.volume_api.scheduler_rpcapi.client)
|
||||
self.stubs.Set(brick_lvm.LVM, '_vg_exists', lambda x: True)
|
||||
self.stubs.Set(tgt.TgtAdm,
|
||||
'create_iscsi_target',
|
||||
self._fake_create_iscsi_target)
|
||||
|
||||
def _fake_create_iscsi_target(self, name, tid, lun,
|
||||
path, chap_auth=None, **kwargs):
|
||||
return 1
|
||||
|
||||
def _issue_volume_reset(self, ctx, volume, updated_status):
|
||||
req = webob.Request.blank('/v2/fake/volumes/%s/action' % volume['id'])
|
||||
|
@ -389,7 +397,8 @@ class AdminActionsTest(test.TestCase):
|
|||
self.assertEqual(admin_metadata[1]['key'], 'attached_mode')
|
||||
self.assertEqual(admin_metadata[1]['value'], 'rw')
|
||||
conn_info = self.volume_api.initialize_connection(ctx,
|
||||
volume, connector)
|
||||
volume,
|
||||
connector)
|
||||
self.assertEqual(conn_info['data']['access_mode'], 'rw')
|
||||
# build request to force detach
|
||||
req = webob.Request.blank('/v2/fake/volumes/%s/action' % volume['id'])
|
||||
|
|
|
@ -38,17 +38,18 @@ class FakeISCSIDriver(lvm.LVMISCSIDriver):
|
|||
|
||||
def initialize_connection(self, volume, connector):
|
||||
volume_metadata = {}
|
||||
|
||||
for metadata in volume['volume_admin_metadata']:
|
||||
volume_metadata[metadata['key']] = metadata['value']
|
||||
|
||||
access_mode = volume_metadata.get('attached_mode')
|
||||
if access_mode is None:
|
||||
access_mode = ('ro'
|
||||
if volume_metadata.get('readonly') == 'True'
|
||||
else 'rw')
|
||||
return {
|
||||
'driver_volume_type': 'iscsi',
|
||||
'data': {'access_mode': access_mode}
|
||||
}
|
||||
|
||||
return {'driver_volume_type': 'iscsi',
|
||||
'data': {'access_mode': access_mode}}
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
pass
|
||||
|
|
|
@ -14,20 +14,24 @@
|
|||
# under the License.
|
||||
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
|
||||
from cinder import context
|
||||
from cinder.db.sqlalchemy import api
|
||||
import cinder.exception
|
||||
import cinder.test
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.drivers.block_device import BlockDeviceDriver
|
||||
from cinder.volume import utils as volutils
|
||||
|
||||
|
||||
class TestBlockDeviceDriver(cinder.test.TestCase):
|
||||
def setUp(self):
|
||||
fake_opt = [cfg.StrOpt('fake_opt', default='fake', help='fake option')]
|
||||
super(TestBlockDeviceDriver, self).setUp()
|
||||
self.configuration = mock.MagicMock()
|
||||
self.configuration = conf.Configuration(fake_opt, 'fake_group')
|
||||
self.configuration.available_devices = ['/dev/loop1', '/dev/loop2']
|
||||
self.configuration.iscsi_helper = 'tgtadm'
|
||||
self.host = 'localhost'
|
||||
self.configuration.iscsi_port = 3260
|
||||
self.configuration.volume_dd_blocksize = 1234
|
||||
|
@ -36,29 +40,49 @@ class TestBlockDeviceDriver(cinder.test.TestCase):
|
|||
|
||||
def test_initialize_connection(self):
|
||||
TEST_VOLUME1 = {'host': 'localhost1',
|
||||
'provider_location': '1 2 3 /dev/loop1'}
|
||||
'provider_location': '1 2 3 /dev/loop1',
|
||||
'provider_auth': 'a b c',
|
||||
'attached_mode': 'rw',
|
||||
'id': 'fake-uuid'}
|
||||
|
||||
TEST_CONNECTOR = {'host': 'localhost1'}
|
||||
|
||||
with mock.patch.object(self.drv, 'local_path',
|
||||
return_value='/dev/loop1') as lp_mocked:
|
||||
data = self.drv.initialize_connection(TEST_VOLUME1, TEST_CONNECTOR)
|
||||
data = self.drv.initialize_connection(TEST_VOLUME1, TEST_CONNECTOR)
|
||||
expected_data = {'data': {'auth_method': 'a',
|
||||
'auth_password': 'c',
|
||||
'auth_username': 'b',
|
||||
'encrypted': False,
|
||||
'target_discovered': False,
|
||||
'target_iqn': '2',
|
||||
'target_lun': 3,
|
||||
'target_portal': '1',
|
||||
'volume_id': 'fake-uuid'},
|
||||
'driver_volume_type': 'iscsi'}
|
||||
|
||||
lp_mocked.assert_called_once_with(TEST_VOLUME1)
|
||||
self.assertEqual(data, {
|
||||
'driver_volume_type': 'local',
|
||||
'data': {'device_path': '/dev/loop1'}})
|
||||
self.assertEqual(expected_data, data)
|
||||
|
||||
@mock.patch('cinder.volume.driver.ISCSIDriver.initialize_connection')
|
||||
def test_initialize_connection_different_hosts(self, _init_conn):
|
||||
TEST_CONNECTOR = {'host': 'localhost1'}
|
||||
TEST_VOLUME2 = {'host': 'localhost2',
|
||||
'provider_location': '1 2 3 /dev/loop2'}
|
||||
'provider_location': '1 2 3 /dev/loop2',
|
||||
'provider_auth': 'd e f',
|
||||
'attached_mode': 'rw',
|
||||
'id': 'fake-uuid-2'}
|
||||
_init_conn.return_value = 'data'
|
||||
|
||||
data = self.drv.initialize_connection(TEST_VOLUME2, TEST_CONNECTOR)
|
||||
expected_data = {'data': {'auth_method': 'd',
|
||||
'auth_password': 'f',
|
||||
'auth_username': 'e',
|
||||
'encrypted': False,
|
||||
'target_discovered': False,
|
||||
'target_iqn': '2',
|
||||
'target_lun': 3,
|
||||
'target_portal': '1',
|
||||
'volume_id': 'fake-uuid-2'}}
|
||||
|
||||
_init_conn.assert_called_once_with(TEST_VOLUME2, TEST_CONNECTOR)
|
||||
self.assertEqual('data', data)
|
||||
self.assertEqual(expected_data['data'], data['data'])
|
||||
|
||||
@mock.patch('cinder.volume.drivers.block_device.BlockDeviceDriver.'
|
||||
'local_path', return_value=None)
|
||||
|
@ -107,7 +131,6 @@ class TestBlockDeviceDriver(cinder.test.TestCase):
|
|||
fasd_mocked.assert_called_once_with(TEST_VOLUME['size'])
|
||||
|
||||
def test_update_volume_stats(self):
|
||||
self.configuration.safe_get.return_value = 'BlockDeviceDriver'
|
||||
|
||||
with mock.patch.object(self.drv, '_devices_sizes',
|
||||
return_value={'/dev/loop1': 1024,
|
||||
|
|
|
@ -1,279 +0,0 @@
|
|||
|
||||
# Copyright 2011 Red Hat, Inc.
|
||||
#
|
||||
# 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 os.path
|
||||
import shutil
|
||||
import string
|
||||
import tempfile
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo_concurrency import processutils
|
||||
|
||||
from cinder.brick.iscsi import iscsi
|
||||
from cinder import test
|
||||
from cinder.volume import driver
|
||||
|
||||
|
||||
class TargetAdminTestCase(object):
|
||||
|
||||
def setUp(self):
|
||||
self.cmds = []
|
||||
|
||||
self.tid = 1
|
||||
self.target_name = 'iqn.2011-09.org.foo.bar:volume-blaa'
|
||||
self.lun = 10
|
||||
self.path = '/foo'
|
||||
self.vol_id = 'blaa'
|
||||
self.vol_name = 'volume-blaa'
|
||||
self.portal = 'portal:3260,1'
|
||||
self.initiator = 'iqn.1994-05.org.foo.bar:test'
|
||||
self.chap_username = 'test_id'
|
||||
self.chap_password = 'test_pass'
|
||||
self.write_cache = 'off'
|
||||
self.db = {}
|
||||
|
||||
self.script_template = None
|
||||
self.stubs.Set(os.path, 'isfile', lambda _: True)
|
||||
self.stubs.Set(os, 'unlink', lambda _: '')
|
||||
self.stubs.Set(iscsi.TgtAdm, '_get_target', self.fake_get_target)
|
||||
self.stubs.Set(iscsi.LioAdm, '_get_target', self.fake_get_target)
|
||||
self.stubs.Set(iscsi.LioAdm,
|
||||
'_verify_rtstool',
|
||||
self.fake_verify_rtstool)
|
||||
self.driver = driver.ISCSIDriver()
|
||||
self.stubs.Set(iscsi.TgtAdm, '_verify_backing_lun',
|
||||
self.fake_verify_backing_lun)
|
||||
self.flags(iscsi_target_prefix='iqn.2011-09.org.foo.bar:')
|
||||
self.persist_tempdir = tempfile.mkdtemp()
|
||||
self.addCleanup(self._cleanup, self.persist_tempdir)
|
||||
|
||||
def fake_verify_backing_lun(obj, iqn, tid):
|
||||
return True
|
||||
|
||||
def fake_verify_rtstool(obj):
|
||||
pass
|
||||
|
||||
def fake_get_target(obj, iqn):
|
||||
return 1
|
||||
|
||||
def get_script_params(self):
|
||||
return {'tid': self.tid,
|
||||
'target_name': self.target_name,
|
||||
'lun': self.lun,
|
||||
'path': self.path,
|
||||
'initiator': self.initiator,
|
||||
'username': self.chap_username,
|
||||
'password': self.chap_password}
|
||||
|
||||
def get_script(self):
|
||||
return self.script_template % self.get_script_params()
|
||||
|
||||
def fake_execute(self, *cmd, **kwargs):
|
||||
self.cmds.append(string.join(cmd))
|
||||
return "", None
|
||||
|
||||
def clear_cmds(self):
|
||||
self.cmds = []
|
||||
|
||||
def verify_config(self):
|
||||
pass
|
||||
|
||||
def verify_cmds(self, cmds):
|
||||
self.assertEqual(len(cmds), len(self.cmds))
|
||||
for cmd in self.cmds:
|
||||
self.assertTrue(cmd in cmds)
|
||||
self.verify_config()
|
||||
|
||||
def verify(self):
|
||||
script = self.get_script()
|
||||
cmds = []
|
||||
for line in script.split('\n'):
|
||||
if not line.strip():
|
||||
continue
|
||||
cmds.append(line)
|
||||
self.verify_cmds(cmds)
|
||||
|
||||
def run_commands(self):
|
||||
target_helper = self.driver.get_target_helper(self.db)
|
||||
target_helper.set_execute(self.fake_execute)
|
||||
chap_auth = target_helper._iscsi_authentication('IncomingUser',
|
||||
self.chap_username,
|
||||
self.chap_password)
|
||||
target_helper.create_iscsi_target(self.target_name, self.tid,
|
||||
self.lun, self.path, chap_auth,
|
||||
write_cache=self.write_cache)
|
||||
target_helper.show_target(self.tid, iqn=self.target_name)
|
||||
if cfg.CONF.iscsi_helper == 'lioadm':
|
||||
volume = {'provider_location': ' '.join([self.portal,
|
||||
self.target_name]),
|
||||
'provider_auth': ' '.join(['CHAP',
|
||||
self.chap_username,
|
||||
self.chap_password])}
|
||||
connector = {'initiator': self.initiator}
|
||||
target_helper.initialize_connection(volume, connector)
|
||||
target_helper.terminate_connection(volume, connector)
|
||||
target_helper.remove_iscsi_target(self.tid, self.lun, self.vol_id,
|
||||
self.vol_name)
|
||||
|
||||
def test_target_admin(self):
|
||||
self.clear_cmds()
|
||||
self.run_commands()
|
||||
self.verify()
|
||||
|
||||
def _cleanup(self, persist_tempdir):
|
||||
try:
|
||||
shutil.rmtree(persist_tempdir)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
class TgtAdmTestCase(test.TestCase, TargetAdminTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TgtAdmTestCase, self).setUp()
|
||||
TargetAdminTestCase.setUp(self)
|
||||
self.flags(iscsi_helper='tgtadm')
|
||||
self.flags(volumes_dir=self.persist_tempdir)
|
||||
self.script_template = "\n".join([
|
||||
'tgt-admin --update %(target_name)s',
|
||||
'tgt-admin --delete %(target_name)s',
|
||||
'tgt-admin --force '
|
||||
'--delete %(target_name)s',
|
||||
'tgtadm --lld iscsi --op show --mode target'])
|
||||
|
||||
def verify_config(self):
|
||||
target_helper = self.driver.get_target_helper(self.db)
|
||||
self.assertEqual(target_helper._get_target_chap_auth(self.target_name),
|
||||
(self.chap_username, self.chap_password))
|
||||
|
||||
def fake_execute(self, *cmd, **kwargs):
|
||||
self.cmds.append(string.join(cmd))
|
||||
# Tests that if tgtadm --op show fails with 'target already exists',
|
||||
# we handle it gracefully and continue.
|
||||
if 'tgtadm' in cmd and '--op' in cmd and 'show' in cmd:
|
||||
raise processutils.ProcessExecutionError(
|
||||
stderr='tgtadm: this target already exists')
|
||||
else:
|
||||
return "", None
|
||||
|
||||
|
||||
class IetAdmTestCase(test.TestCase, TargetAdminTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(IetAdmTestCase, self).setUp()
|
||||
TargetAdminTestCase.setUp(self)
|
||||
|
||||
self.iet_conffile = str(self.persist_tempdir) + '/bogus-file'
|
||||
|
||||
self.flags(iscsi_helper='ietadm')
|
||||
self.flags(iet_conf=self.iet_conffile)
|
||||
|
||||
self.script_template = "\n".join([
|
||||
'ietadm --op new --tid=%(tid)s --params Name=%(target_name)s',
|
||||
'ietadm --op new --tid=%(tid)s --lun=%(lun)s '
|
||||
'--params Path=%(path)s,Type=fileio',
|
||||
'ietadm --op new --tid=%(tid)s --user '
|
||||
'--params=IncomingUser=%(username)s,Password=%(password)s',
|
||||
'ietadm --op show --tid=%(tid)s',
|
||||
'ietadm --op delete --tid=%(tid)s --lun=%(lun)s',
|
||||
'ietadm --op delete --tid=%(tid)s'])
|
||||
|
||||
|
||||
class IetAdmBlockIOTestCase(test.TestCase, TargetAdminTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(IetAdmBlockIOTestCase, self).setUp()
|
||||
TargetAdminTestCase.setUp(self)
|
||||
|
||||
self.iet_conffile = str(self.persist_tempdir) + '/bogus-file'
|
||||
|
||||
self.flags(iscsi_helper='ietadm')
|
||||
self.flags(iscsi_iotype='blockio')
|
||||
self.flags(iet_conf=self.iet_conffile)
|
||||
self.script_template = "\n".join([
|
||||
'ietadm --op new --tid=%(tid)s --params Name=%(target_name)s',
|
||||
'ietadm --op new --tid=%(tid)s --lun=%(lun)s '
|
||||
'--params Path=%(path)s,Type=blockio',
|
||||
'ietadm --op new --tid=%(tid)s --user '
|
||||
'--params=IncomingUser=%(username)s,Password=%(password)s',
|
||||
'ietadm --op show --tid=%(tid)s',
|
||||
'ietadm --op delete --tid=%(tid)s --lun=%(lun)s',
|
||||
'ietadm --op delete --tid=%(tid)s'])
|
||||
|
||||
|
||||
class IetAdmFileIOTestCase(test.TestCase, TargetAdminTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(IetAdmFileIOTestCase, self).setUp()
|
||||
TargetAdminTestCase.setUp(self)
|
||||
|
||||
self.iet_conffile = str(self.persist_tempdir) + '/bogus-file'
|
||||
|
||||
self.flags(iet_conf=self.iet_conffile)
|
||||
self.flags(iscsi_helper='ietadm')
|
||||
self.flags(iscsi_iotype='fileio')
|
||||
self.script_template = "\n".join([
|
||||
'ietadm --op new --tid=%(tid)s --params Name=%(target_name)s',
|
||||
'ietadm --op new --tid=%(tid)s --lun=%(lun)s '
|
||||
'--params Path=%(path)s,Type=fileio',
|
||||
'ietadm --op new --tid=%(tid)s --user '
|
||||
'--params=IncomingUser=%(username)s,Password=%(password)s',
|
||||
'ietadm --op show --tid=%(tid)s',
|
||||
'ietadm --op delete --tid=%(tid)s --lun=%(lun)s',
|
||||
'ietadm --op delete --tid=%(tid)s'])
|
||||
|
||||
|
||||
class IetAdmAutoIOTestCase(test.TestCase, TargetAdminTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(IetAdmAutoIOTestCase, self).setUp()
|
||||
TargetAdminTestCase.setUp(self)
|
||||
self.iet_conffile = 'this-bogus-conf-file-dne'
|
||||
self.stubs.Set(iscsi.IetAdm, '_is_block', lambda a, b: True)
|
||||
|
||||
self.flags(iscsi_helper='ietadm')
|
||||
self.flags(iscsi_iotype='auto')
|
||||
self.flags(iet_conf=self.iet_conffile)
|
||||
self.script_template = "\n".join([
|
||||
'ietadm --op new --tid=%(tid)s --params Name=%(target_name)s',
|
||||
'ietadm --op new --tid=%(tid)s --lun=%(lun)s '
|
||||
'--params Path=%(path)s,Type=blockio',
|
||||
'ietadm --op new --tid=%(tid)s --user '
|
||||
'--params=IncomingUser=%(username)s,Password=%(password)s',
|
||||
'ietadm --op show --tid=%(tid)s',
|
||||
'ietadm --op delete --tid=%(tid)s --lun=%(lun)s',
|
||||
'ietadm --op delete --tid=%(tid)s'])
|
||||
|
||||
|
||||
class LioAdmTestCase(test.TestCase, TargetAdminTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(LioAdmTestCase, self).setUp()
|
||||
TargetAdminTestCase.setUp(self)
|
||||
self.flags(iscsi_helper='lioadm')
|
||||
self.script_template = "\n".join([
|
||||
'cinder-rtstool create '
|
||||
'%(path)s %(target_name)s %(username)s %(password)s',
|
||||
'cinder-rtstool add-initiator '
|
||||
'%(target_name)s %(username)s %(password)s %(initiator)s',
|
||||
'cinder-rtstool delete-initiator %(target_name)s %(initiator)s',
|
||||
'cinder-rtstool delete %(target_name)s'])
|
||||
|
||||
|
||||
class ISERTgtAdmTestCase(TgtAdmTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ISERTgtAdmTestCase, self).setUp()
|
||||
self.flags(iscsi_helper='iseradm')
|
|
@ -48,6 +48,7 @@ class SolidFireVolumeTestCase(test.TestCase):
|
|||
self.configuration.sf_emulate_512 = True
|
||||
self.configuration.sf_account_prefix = 'cinder'
|
||||
self.configuration.reserved_percentage = 25
|
||||
self.configuration.iscsi_helper = None
|
||||
|
||||
super(SolidFireVolumeTestCase, self).setUp()
|
||||
self.stubs.Set(SolidFireDriver, '_issue_api_request',
|
||||
|
|
|
@ -38,7 +38,6 @@ from stevedore import extension
|
|||
from taskflow.engines.action_engine import engine
|
||||
|
||||
from cinder.backup import driver as backup_driver
|
||||
from cinder.brick.iscsi import iscsi
|
||||
from cinder.brick.local_dev import lvm as brick_lvm
|
||||
from cinder import context
|
||||
from cinder import db
|
||||
|
@ -64,6 +63,7 @@ from cinder.volume import driver
|
|||
from cinder.volume.drivers import lvm
|
||||
from cinder.volume.manager import VolumeManager
|
||||
from cinder.volume import rpcapi as volume_rpcapi
|
||||
from cinder.volume.targets import tgt
|
||||
from cinder.volume import utils as volutils
|
||||
from cinder.volume import volume_types
|
||||
|
||||
|
@ -75,9 +75,6 @@ CONF = cfg.CONF
|
|||
|
||||
ENCRYPTION_PROVIDER = 'nova.volume.encryptors.cryptsetup.CryptsetupEncryptor'
|
||||
PLATFORM = platform
|
||||
fake_opt = [
|
||||
cfg.StrOpt('fake_opt', default='fake', help='fake opts')
|
||||
]
|
||||
|
||||
FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa'
|
||||
|
||||
|
@ -108,6 +105,8 @@ class BaseVolumeTestCase(test.TestCase):
|
|||
mock_decorator = mock.MagicMock(side_effect=side_effect)
|
||||
mock_trace_cls.return_value = mock_decorator
|
||||
self.volume = importutils.import_object(CONF.volume_manager)
|
||||
self.configuration = mock.Mock(conf.Configuration)
|
||||
#self.configuration = conf.Configuration(fake_opts, 'fake_group')
|
||||
self.context = context.get_admin_context()
|
||||
self.context.user_id = 'fake'
|
||||
self.context.project_id = 'fake'
|
||||
|
@ -115,7 +114,6 @@ class BaseVolumeTestCase(test.TestCase):
|
|||
'status': 'creating',
|
||||
'host': CONF.host,
|
||||
'size': 1}
|
||||
self.stubs.Set(iscsi.TgtAdm, '_get_target', self.fake_get_target)
|
||||
self.stubs.Set(brick_lvm.LVM,
|
||||
'get_all_volume_groups',
|
||||
self.fake_get_all_volume_groups)
|
||||
|
@ -231,12 +229,20 @@ class AvailabilityZoneTestCase(BaseVolumeTestCase):
|
|||
|
||||
class VolumeTestCase(BaseVolumeTestCase):
|
||||
|
||||
def _fake_create_iscsi_target(self, name, tid,
|
||||
lun, path, chap_auth=None,
|
||||
**kwargs):
|
||||
return 1
|
||||
|
||||
def setUp(self):
|
||||
super(VolumeTestCase, self).setUp()
|
||||
self.stubs.Set(volutils, 'clear_volume',
|
||||
lambda a, b, volume_clear=mox.IgnoreArg(),
|
||||
volume_clear_size=mox.IgnoreArg(),
|
||||
lvm_type=mox.IgnoreArg(): None)
|
||||
self.stubs.Set(tgt.TgtAdm,
|
||||
'create_iscsi_target',
|
||||
self._fake_create_iscsi_target)
|
||||
|
||||
def test_init_host_clears_downloads(self):
|
||||
"""Test that init_host will unwedge a volume stuck in downloading."""
|
||||
|
@ -282,9 +288,16 @@ class VolumeTestCase(BaseVolumeTestCase):
|
|||
self.assertEqual(
|
||||
stats['pools']['pool2']['allocated_capacity_gb'], 1024)
|
||||
|
||||
# NOTE(jdg): On the create we have host='xyz', BUT
|
||||
# here we do a db.volume_get, and now the host has
|
||||
# been updated to xyz#pool-name. Note this is
|
||||
# done via the managers init, which calls the drivers
|
||||
# get_pool method, which in the legacy case is going
|
||||
# to be volume_backend_name or None
|
||||
|
||||
vol0 = db.volume_get(context.get_admin_context(), vol0['id'])
|
||||
self.assertEqual(vol0['host'],
|
||||
volutils.append_host(CONF.host, 'LVM_iSCSI'))
|
||||
volutils.append_host(CONF.host, 'LVM'))
|
||||
self.volume.delete_volume(self.context, vol0['id'])
|
||||
self.volume.delete_volume(self.context, vol1['id'])
|
||||
self.volume.delete_volume(self.context, vol2['id'])
|
||||
|
@ -1416,6 +1429,7 @@ class VolumeTestCase(BaseVolumeTestCase):
|
|||
for item in admin_metadata:
|
||||
ret.update({item['key']: item['value']})
|
||||
self.assertDictMatch(ret, expected)
|
||||
|
||||
connector = {'initiator': 'iqn.2012-07.org.fake:01'}
|
||||
conn_info = self.volume.initialize_connection(self.context,
|
||||
volume_id, connector)
|
||||
|
@ -1510,6 +1524,7 @@ class VolumeTestCase(BaseVolumeTestCase):
|
|||
connector = {'initiator': 'iqn.2012-07.org.fake:01'}
|
||||
conn_info = self.volume.initialize_connection(self.context,
|
||||
volume_id, connector)
|
||||
|
||||
self.assertEqual(conn_info['data']['access_mode'], 'ro')
|
||||
|
||||
self.volume.detach_volume(self.context, volume_id)
|
||||
|
@ -3235,10 +3250,10 @@ class VolumeTestCase(BaseVolumeTestCase):
|
|||
group_id = group['id']
|
||||
volume = tests_utils.create_volume(
|
||||
self.context,
|
||||
consistencygroup_id = group_id,
|
||||
consistencygroup_id=group_id,
|
||||
host='host1@backend1#pool1',
|
||||
status = 'creating',
|
||||
size = 1)
|
||||
status='creating',
|
||||
size=1)
|
||||
self.volume.host = 'host1@backend1'
|
||||
volume_id = volume['id']
|
||||
self.volume.create_volume(self.context, volume_id)
|
||||
|
@ -3272,10 +3287,10 @@ class VolumeTestCase(BaseVolumeTestCase):
|
|||
group_id = group['id']
|
||||
volume = tests_utils.create_volume(
|
||||
self.context,
|
||||
consistencygroup_id = group_id,
|
||||
consistencygroup_id=group_id,
|
||||
host='host1@backend1#pool1',
|
||||
status = 'creating',
|
||||
size = 1)
|
||||
status='creating',
|
||||
size=1)
|
||||
self.volume.host = 'host1@backend2'
|
||||
volume_id = volume['id']
|
||||
self.volume.create_volume(self.context, volume_id)
|
||||
|
@ -3576,7 +3591,7 @@ class DriverTestCase(test.TestCase):
|
|||
self.volume = importutils.import_object(CONF.volume_manager)
|
||||
self.context = context.get_admin_context()
|
||||
self.output = ""
|
||||
self.stubs.Set(iscsi.TgtAdm, '_get_target', self.fake_get_target)
|
||||
self.configuration = conf.Configuration(None)
|
||||
self.stubs.Set(brick_lvm.LVM, '_vg_exists', lambda x: True)
|
||||
|
||||
def _fake_execute(_command, *_args, **_kwargs):
|
||||
|
@ -3826,7 +3841,7 @@ class LVMISCSIVolumeDriverTestCase(DriverTestCase):
|
|||
self.stubs.Set(self.volume.driver, '_delete_volume',
|
||||
lambda x: None)
|
||||
|
||||
self.stubs.Set(self.volume.driver, '_create_export',
|
||||
self.stubs.Set(self.volume.driver, 'create_export',
|
||||
lambda x, y, vg='vg': None)
|
||||
|
||||
self.volume.driver.vg = FakeBrickLVM('cinder-volumes',
|
||||
|
@ -3919,11 +3934,12 @@ class LVMVolumeDriverTestCase(DriverTestCase):
|
|||
FAKE_VOLUME = {'name': 'test1',
|
||||
'id': 'test1'}
|
||||
|
||||
def test_delete_volume_invalid_parameter(self):
|
||||
configuration = conf.Configuration(fake_opt, 'fake_group')
|
||||
configuration.volume_clear = 'zero'
|
||||
configuration.volume_clear_size = 0
|
||||
lvm_driver = lvm.LVMVolumeDriver(configuration=configuration)
|
||||
@mock.patch.object(fake_driver.FakeISCSIDriver, 'create_export')
|
||||
def test_delete_volume_invalid_parameter(self, _mock_create_export):
|
||||
self.configuration.volume_clear = 'zero'
|
||||
self.configuration.volume_clear_size = 0
|
||||
|
||||
lvm_driver = lvm.LVMVolumeDriver(configuration=self.configuration)
|
||||
self.mox.StubOutWithMock(os.path, 'exists')
|
||||
|
||||
os.path.exists(mox.IgnoreArg()).AndReturn(True)
|
||||
|
@ -3935,12 +3951,14 @@ class LVMVolumeDriverTestCase(DriverTestCase):
|
|||
lvm_driver._delete_volume,
|
||||
self.FAKE_VOLUME)
|
||||
|
||||
def test_delete_volume_bad_path(self):
|
||||
configuration = conf.Configuration(fake_opt, 'fake_group')
|
||||
configuration.volume_clear = 'zero'
|
||||
configuration.volume_clear_size = 0
|
||||
@mock.patch.object(fake_driver.FakeISCSIDriver, 'create_export')
|
||||
def test_delete_volume_bad_path(self, _mock_create_export):
|
||||
self.configuration.volume_clear = 'zero'
|
||||
self.configuration.volume_clear_size = 0
|
||||
self.configuration.volume_type = 'default'
|
||||
|
||||
volume = dict(self.FAKE_VOLUME, size=1)
|
||||
lvm_driver = lvm.LVMVolumeDriver(configuration=configuration)
|
||||
lvm_driver = lvm.LVMVolumeDriver(configuration=self.configuration)
|
||||
|
||||
self.mox.StubOutWithMock(os.path, 'exists')
|
||||
os.path.exists(mox.IgnoreArg()).AndReturn(False)
|
||||
|
@ -3949,12 +3967,13 @@ class LVMVolumeDriverTestCase(DriverTestCase):
|
|||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
lvm_driver._delete_volume, volume)
|
||||
|
||||
def test_delete_volume_thinlvm_snap(self):
|
||||
configuration = conf.Configuration(fake_opt, 'fake_group')
|
||||
configuration.volume_clear = 'zero'
|
||||
configuration.volume_clear_size = 0
|
||||
configuration.lvm_type = 'thin'
|
||||
lvm_driver = lvm.LVMISCSIDriver(configuration=configuration,
|
||||
@mock.patch.object(fake_driver.FakeISCSIDriver, 'create_export')
|
||||
def test_delete_volume_thinlvm_snap(self, _mock_create_export):
|
||||
self.configuration.volume_clear = 'zero'
|
||||
self.configuration.volume_clear_size = 0
|
||||
self.configuration.lvm_type = 'thin'
|
||||
self.configuration.iscsi_helper = 'tgtadm'
|
||||
lvm_driver = lvm.LVMISCSIDriver(configuration=self.configuration,
|
||||
vg_obj=mox.MockAnything())
|
||||
|
||||
# Ensures that copy_volume is not called for ThinLVM
|
||||
|
@ -3975,7 +3994,6 @@ class LVMVolumeDriverTestCase(DriverTestCase):
|
|||
class ISCSITestCase(DriverTestCase):
|
||||
"""Test Case for ISCSIDriver"""
|
||||
driver_name = "cinder.volume.drivers.lvm.LVMISCSIDriver"
|
||||
base_driver = driver.ISCSIDriver
|
||||
|
||||
def setUp(self):
|
||||
super(ISCSITestCase, self).setUp()
|
||||
|
@ -4005,12 +4023,15 @@ class ISCSITestCase(DriverTestCase):
|
|||
return volume_id_list
|
||||
|
||||
def test_do_iscsi_discovery(self):
|
||||
self.configuration.append_config_values(mox.IgnoreArg())
|
||||
iscsi_driver = self.base_driver(configuration=self.configuration)
|
||||
self.configuration = conf.Configuration(None)
|
||||
iscsi_driver = \
|
||||
cinder.volume.targets.tgt.TgtAdm(
|
||||
configuration=self.configuration)
|
||||
iscsi_driver._execute = lambda *a, **kw: \
|
||||
("%s dummy" % CONF.iscsi_ip_address, '')
|
||||
volume = {"name": "dummy",
|
||||
"host": "0.0.0.0"}
|
||||
"host": "0.0.0.0",
|
||||
"id": "12345678-1234-5678-1234-567812345678"}
|
||||
iscsi_driver._do_iscsi_discovery(volume)
|
||||
|
||||
def test_get_iscsi_properties(self):
|
||||
|
@ -4018,7 +4039,8 @@ class ISCSITestCase(DriverTestCase):
|
|||
"id": "0",
|
||||
"provider_auth": "a b c",
|
||||
"attached_mode": "rw"}
|
||||
iscsi_driver = self.base_driver(configuration=self.configuration)
|
||||
iscsi_driver = \
|
||||
cinder.volume.targets.tgt.TgtAdm(configuration=self.configuration)
|
||||
iscsi_driver._do_iscsi_discovery = lambda v: "0.0.0.0:0000,0 iqn:iqn 0"
|
||||
result = iscsi_driver._get_iscsi_properties(volume)
|
||||
self.assertEqual(result["target_portal"], "0.0.0.0:0000")
|
||||
|
@ -4057,7 +4079,10 @@ class ISCSITestCase(DriverTestCase):
|
|||
stats['pools'][0]['free_capacity_gb'], float('0.52'))
|
||||
|
||||
def test_validate_connector(self):
|
||||
iscsi_driver = self.base_driver(configuration=self.configuration)
|
||||
iscsi_driver =\
|
||||
cinder.volume.targets.tgt.TgtAdm(
|
||||
configuration=self.configuration)
|
||||
|
||||
# Validate a valid connector
|
||||
connector = {'ip': '10.0.0.2',
|
||||
'host': 'fakehost',
|
||||
|
@ -4073,7 +4098,6 @@ class ISCSITestCase(DriverTestCase):
|
|||
class ISERTestCase(DriverTestCase):
|
||||
"""Test Case for ISERDriver."""
|
||||
driver_name = "cinder.volume.drivers.lvm.LVMISERDriver"
|
||||
base_driver = driver.ISERDriver
|
||||
|
||||
def setUp(self):
|
||||
super(ISERTestCase, self).setUp()
|
||||
|
@ -4084,7 +4108,10 @@ class ISERTestCase(DriverTestCase):
|
|||
self.configuration.iser_target_prefix = 'iqn.2010-10.org.openstack:'
|
||||
self.configuration.iser_ip_address = '0.0.0.0'
|
||||
self.configuration.iser_port = 3260
|
||||
self.configuration.target_driver = \
|
||||
'cinder.volume.targets.iser.ISERTgtAdm'
|
||||
|
||||
@test.testtools.skip("SKIP until ISER driver is removed or fixed")
|
||||
def test_get_volume_stats(self):
|
||||
def _fake_get_all_physical_volumes(obj, root_helper, vg_name):
|
||||
return [{}]
|
||||
|
@ -4103,6 +4130,9 @@ class ISERTestCase(DriverTestCase):
|
|||
self.stubs.Set(brick_lvm.LVM,
|
||||
'get_all_volume_groups',
|
||||
_fake_get_all_volume_groups)
|
||||
|
||||
self.volume_driver = \
|
||||
lvm.LVMISERDriver(configuration=self.configuration)
|
||||
self.volume.driver.vg = brick_lvm.LVM('cinder-volumes', 'sudo')
|
||||
|
||||
stats = self.volume.driver.get_volume_stats(refresh=True)
|
||||
|
@ -4113,8 +4143,9 @@ class ISERTestCase(DriverTestCase):
|
|||
stats['pools'][0]['free_capacity_gb'], float('0.52'))
|
||||
self.assertEqual(stats['storage_protocol'], 'iSER')
|
||||
|
||||
@test.testtools.skip("SKIP until ISER driver is removed or fixed")
|
||||
def test_get_volume_stats2(self):
|
||||
iser_driver = self.base_driver(configuration=self.configuration)
|
||||
iser_driver = lvm.LVMISERDriver(configuration=self.configuration)
|
||||
|
||||
stats = iser_driver.get_volume_stats(refresh=True)
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ from cinder.image import image_utils
|
|||
from cinder.openstack.common import fileutils
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import utils
|
||||
from cinder.volume import iscsi
|
||||
from cinder.volume import rpcapi as volume_rpcapi
|
||||
from cinder.volume import utils as volume_utils
|
||||
|
||||
|
@ -202,6 +201,16 @@ class VolumeDriver(object):
|
|||
|
||||
self.pools = []
|
||||
|
||||
# We set these mappings up in the base driver so they
|
||||
# can be used by children
|
||||
# (intended for LVM and BlockDevice, but others could use as well)
|
||||
self.target_mapping = {
|
||||
'fake': 'cinder.volume.targets.fake.FakeTarget',
|
||||
'ietadm': 'cinder.volume.targets.iet.IetAdm',
|
||||
'iseradm': 'cinder.volume.targets.iser.ISERTgtAdm',
|
||||
'lioadm': 'cinder.volume.targets.lio.LioAdm',
|
||||
'tgtadm': 'cinder.volume.targets.tgt.TgtAdm', }
|
||||
|
||||
# set True by manager after successful check_for_setup
|
||||
self._initialized = False
|
||||
|
||||
|
@ -1043,6 +1052,10 @@ class ISCSIDriver(VolumeDriver):
|
|||
}
|
||||
|
||||
"""
|
||||
# NOTE(jdg): Yes, this is duplicated in the volume/target
|
||||
# drivers, for now leaving it as there are 3'rd party
|
||||
# drivers that don't use target drivers, but inherit from
|
||||
# this base class and use this init data
|
||||
iscsi_properties = self._get_iscsi_properties(volume)
|
||||
return {
|
||||
'driver_volume_type': 'iscsi',
|
||||
|
@ -1106,26 +1119,6 @@ class ISCSIDriver(VolumeDriver):
|
|||
data["pools"].append(single_pool)
|
||||
self._stats = data
|
||||
|
||||
def get_target_helper(self, db):
|
||||
root_helper = utils.get_root_helper()
|
||||
# FIXME(jdg): These work because the driver will overide
|
||||
# but we need to move these to use self.configuraiton
|
||||
if CONF.iscsi_helper == 'iseradm':
|
||||
return iscsi.ISERTgtAdm(root_helper, CONF.volumes_dir,
|
||||
CONF.iscsi_target_prefix, db=db)
|
||||
elif CONF.iscsi_helper == 'tgtadm':
|
||||
return iscsi.TgtAdm(root_helper,
|
||||
CONF.volumes_dir,
|
||||
CONF.iscsi_target_prefix,
|
||||
db=db)
|
||||
elif CONF.iscsi_helper == 'fake':
|
||||
return iscsi.FakeIscsiHelper()
|
||||
elif CONF.iscsi_helper == 'lioadm':
|
||||
return iscsi.LioAdm(root_helper, CONF.iscsi_target_prefix, db=db)
|
||||
else:
|
||||
return iscsi.IetAdm(root_helper, CONF.iet_conf, CONF.iscsi_iotype,
|
||||
db=db)
|
||||
|
||||
|
||||
class FakeISCSIDriver(ISCSIDriver):
|
||||
"""Logs calls instead of executing."""
|
||||
|
@ -1282,15 +1275,6 @@ class ISERDriver(ISCSIDriver):
|
|||
data["pools"].append(single_pool)
|
||||
self._stats = data
|
||||
|
||||
def get_target_helper(self, db):
|
||||
root_helper = utils.get_root_helper()
|
||||
|
||||
if CONF.iser_helper == 'fake':
|
||||
return iscsi.FakeIscsiHelper()
|
||||
else:
|
||||
return iscsi.ISERTgtAdm(root_helper,
|
||||
CONF.volumes_dir, db=db)
|
||||
|
||||
|
||||
class FakeISERDriver(FakeISCSIDriver):
|
||||
"""Logs calls instead of executing."""
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
import os
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.utils import importutils
|
||||
|
||||
from cinder import context
|
||||
from cinder.db.sqlalchemy import api
|
||||
|
@ -39,18 +40,21 @@ CONF = cfg.CONF
|
|||
CONF.register_opts(volume_opts)
|
||||
|
||||
|
||||
class BlockDeviceDriver(driver.ISCSIDriver):
|
||||
VERSION = '1.0.0'
|
||||
class BlockDeviceDriver(driver.VolumeDriver):
|
||||
VERSION = '2.0.0'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.target_helper = self.get_target_helper(kwargs.get('db'))
|
||||
super(BlockDeviceDriver, self).__init__(*args, **kwargs)
|
||||
self.configuration.append_config_values(volume_opts)
|
||||
|
||||
def set_execute(self, execute):
|
||||
super(BlockDeviceDriver, self).set_execute(execute)
|
||||
if self.target_helper is not None:
|
||||
self.target_helper.set_execute(execute)
|
||||
self.backend_name = \
|
||||
self.configuration.safe_get('volume_backend_name') or "BlockDev"
|
||||
target_driver =\
|
||||
self.target_mapping[self.configuration.safe_get('iscsi_helper')]
|
||||
self.target_driver = importutils.import_object(
|
||||
target_driver,
|
||||
configuration=self.configuration,
|
||||
db=self.db,
|
||||
executor=self._execute)
|
||||
|
||||
def check_for_setup_error(self):
|
||||
pass
|
||||
|
@ -62,46 +66,6 @@ class BlockDeviceDriver(driver.ISCSIDriver):
|
|||
'provider_location': device,
|
||||
}
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
if connector['host'] != volume['host']:
|
||||
return super(BlockDeviceDriver, self). \
|
||||
initialize_connection(volume, connector)
|
||||
else:
|
||||
return {
|
||||
'driver_volume_type': 'local',
|
||||
'data': {'device_path': self.local_path(volume)},
|
||||
}
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
pass
|
||||
|
||||
def create_export(self, context, volume):
|
||||
"""Creates an export for a logical volume."""
|
||||
volume_path = self.local_path(volume)
|
||||
data = self.target_helper.create_export(context,
|
||||
volume,
|
||||
volume_path,
|
||||
self.configuration)
|
||||
return {
|
||||
'provider_location': data['location'] + ' ' + volume_path,
|
||||
'provider_auth': data['auth'],
|
||||
}
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
self.target_helper.remove_export(context, volume)
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
volume_name = volume['name']
|
||||
|
||||
iscsi_name = "%s%s" % (self.configuration.iscsi_target_prefix,
|
||||
volume_name)
|
||||
volume_path = self.local_path(volume)
|
||||
|
||||
# NOTE(jdg): For TgtAdm case iscsi_name is the ONLY param we need
|
||||
# should clean this all up at some point in the future
|
||||
self.target_helper.ensure_export(context, volume, iscsi_name,
|
||||
volume_path)
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a logical volume."""
|
||||
dev_path = self.local_path(volume)
|
||||
|
@ -221,3 +185,36 @@ class BlockDeviceDriver(driver.ISCSIDriver):
|
|||
return possible_device
|
||||
else:
|
||||
raise exception.CinderException(_("No big enough free disk"))
|
||||
|
||||
# ####### Interface methods for DataPath (Target Driver) ########
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
volume_name = volume['name']
|
||||
volume_path = "/dev/%s/%s" % (self.configuration.volume_group,
|
||||
volume_name)
|
||||
model_update = \
|
||||
self.target_driver.ensure_export(context,
|
||||
volume,
|
||||
volume_path=volume_path)
|
||||
return model_update
|
||||
|
||||
def create_export(self, context, volume):
|
||||
volume_path = "/dev/%s/%s" % (self.configuration.volume_group,
|
||||
volume['name'])
|
||||
export_info = self.target_driver.create_export(context,
|
||||
volume,
|
||||
volume_path)
|
||||
return {'provider_location': export_info['location'],
|
||||
'provider_auth': export_info['auth'], }
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
self.target_driver.remove_export(context, volume)
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
return self.target_driver.initialize_connection(volume, connector)
|
||||
|
||||
def validate_connector(self, connector):
|
||||
return self.target_driver.validate_connector(connector)
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
pass
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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
|
||||
|
@ -13,6 +9,7 @@
|
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Driver for Linux servers running LVM.
|
||||
|
||||
|
@ -23,6 +20,7 @@ import os
|
|||
import socket
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.utils import importutils
|
||||
from oslo.utils import units
|
||||
from oslo_concurrency import processutils
|
||||
|
||||
|
@ -39,6 +37,10 @@ from cinder.volume import utils as volutils
|
|||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
# FIXME(jdg): We'll put the lvm_ prefix back on these when we
|
||||
# move over to using this as the real LVM driver, for now we'll
|
||||
# rename them so that the config generation utility doesn't barf
|
||||
# on duplicate entries.
|
||||
volume_opts = [
|
||||
cfg.StrOpt('volume_group',
|
||||
default='cinder-volumes',
|
||||
|
@ -59,60 +61,35 @@ CONF.register_opts(volume_opts)
|
|||
class LVMVolumeDriver(driver.VolumeDriver):
|
||||
"""Executes commands relating to Volumes."""
|
||||
|
||||
VERSION = '2.0.0'
|
||||
VERSION = '3.0.0'
|
||||
|
||||
def __init__(self, vg_obj=None, *args, **kwargs):
|
||||
# Parent sets db, host, _execute and base config
|
||||
super(LVMVolumeDriver, self).__init__(*args, **kwargs)
|
||||
|
||||
self.configuration.append_config_values(volume_opts)
|
||||
self.hostname = socket.gethostname()
|
||||
self.vg = vg_obj
|
||||
self.backend_name =\
|
||||
self.configuration.safe_get('volume_backend_name') or 'LVM'
|
||||
self.protocol = 'local'
|
||||
|
||||
def set_execute(self, execute):
|
||||
self._execute = execute
|
||||
# Target Driver is what handles data-transport
|
||||
# Transport specific code should NOT be in
|
||||
# the driver (control path), this way
|
||||
# different target drivers can be added (iscsi, FC etc)
|
||||
target_driver = \
|
||||
self.target_mapping[self.configuration.safe_get('iscsi_helper')]
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Verify that requirements are in place to use LVM driver."""
|
||||
if self.vg is None:
|
||||
root_helper = utils.get_root_helper()
|
||||
try:
|
||||
self.vg = lvm.LVM(self.configuration.volume_group,
|
||||
root_helper,
|
||||
lvm_type=self.configuration.lvm_type,
|
||||
executor=self._execute)
|
||||
except brick_exception.VolumeGroupNotFound:
|
||||
message = ("Volume Group %s does not exist" %
|
||||
self.configuration.volume_group)
|
||||
raise exception.VolumeBackendAPIException(data=message)
|
||||
LOG.debug('Attempting to initialize LVM driver with the '
|
||||
'following target_driver: %s',
|
||||
target_driver)
|
||||
|
||||
vg_list = volutils.get_all_volume_groups(
|
||||
self.configuration.volume_group)
|
||||
vg_dict = \
|
||||
(vg for vg in vg_list if vg['name'] == self.vg.vg_name).next()
|
||||
if vg_dict is None:
|
||||
message = ("Volume Group %s does not exist" %
|
||||
self.configuration.volume_group)
|
||||
raise exception.VolumeBackendAPIException(data=message)
|
||||
|
||||
if self.configuration.lvm_type == 'thin':
|
||||
# Specific checks for using Thin provisioned LV's
|
||||
if not volutils.supports_thin_provisioning():
|
||||
message = ("Thin provisioning not supported "
|
||||
"on this version of LVM.")
|
||||
raise exception.VolumeBackendAPIException(data=message)
|
||||
|
||||
pool_name = "%s-pool" % self.configuration.volume_group
|
||||
if self.vg.get_volume(pool_name) is None:
|
||||
try:
|
||||
self.vg.create_thin_pool(pool_name)
|
||||
except processutils.ProcessExecutionError as exc:
|
||||
exception_message = ("Failed to create thin pool, "
|
||||
"error message was: %s"
|
||||
% exc.stderr)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exception_message)
|
||||
self.target_driver = importutils.import_object(
|
||||
target_driver,
|
||||
configuration=self.configuration,
|
||||
db=self.db,
|
||||
executor=self._execute)
|
||||
self.protocol = self.target_driver.protocol
|
||||
|
||||
def _sizestr(self, size_in_g):
|
||||
return '%sg' % size_in_g
|
||||
|
@ -148,15 +125,15 @@ class LVMVolumeDriver(driver.VolumeDriver):
|
|||
# the cow table and only overwriting what's necessary?
|
||||
# for now we're still skipping on snaps due to hang issue
|
||||
if not os.path.exists(dev_path):
|
||||
msg = (_('Volume device file path %s does not exist.')
|
||||
msg = (_LE('Volume device file path %s does not exist.')
|
||||
% dev_path)
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
size_in_g = volume.get('size', volume.get('volume_size', None))
|
||||
if size_in_g is None:
|
||||
msg = (_("Size for volume: %s not found, "
|
||||
"cannot secure delete.") % volume['id'])
|
||||
msg = (_LE("Size for volume: %s not found, "
|
||||
"cannot secure delete.") % volume['id'])
|
||||
LOG.error(msg)
|
||||
raise exception.InvalidParameterValue(msg)
|
||||
|
||||
|
@ -183,6 +160,106 @@ class LVMVolumeDriver(driver.VolumeDriver):
|
|||
|
||||
vg_ref.create_volume(name, size, lvm_type, mirror_count)
|
||||
|
||||
def _update_volume_stats(self):
|
||||
"""Retrieve stats info from volume group."""
|
||||
|
||||
LOG.debug(("Updating volume stats"))
|
||||
if self.vg is None:
|
||||
LOG.warning(_LW('Unable to update stats on non-initialized '
|
||||
'Volume Group: %s'),
|
||||
self.configuration.volume_group)
|
||||
return
|
||||
|
||||
self.vg.update_volume_group_info()
|
||||
data = {}
|
||||
|
||||
# Note(zhiteng): These information are driver/backend specific,
|
||||
# each driver may define these values in its own config options
|
||||
# or fetch from driver specific configuration file.
|
||||
data["volume_backend_name"] = self.backend_name
|
||||
data["vendor_name"] = 'Open Source'
|
||||
data["driver_version"] = self.VERSION
|
||||
data["storage_protocol"] = self.protocol
|
||||
data["pools"] = []
|
||||
|
||||
total_capacity = 0
|
||||
free_capacity = 0
|
||||
if self.configuration.lvm_mirrors > 0:
|
||||
total_capacity =\
|
||||
self.vg.vg_mirror_size(self.configuration.lvm_mirrors)
|
||||
free_capacity =\
|
||||
self.vg.vg_mirror_free_space(self.configuration.lvm_mirrors)
|
||||
elif self.configuration.lvm_type == 'thin':
|
||||
total_capacity = self.vg.vg_thin_pool_size
|
||||
free_capacity = self.vg.vg_thin_pool_free_space
|
||||
else:
|
||||
total_capacity = self.vg.vg_size
|
||||
free_capacity = self.vg.vg_free_space
|
||||
|
||||
location_info = \
|
||||
('LVMVolumeDriver:%(hostname)s:%(vg)s'
|
||||
':%(lvm_type)s:%(lvm_mirrors)s' %
|
||||
{'hostname': self.hostname,
|
||||
'vg': self.configuration.volume_group,
|
||||
'lvm_type': self.configuration.lvm_type,
|
||||
'lvm_mirrors': self.configuration.lvm_mirrors})
|
||||
|
||||
# Skip enabled_pools setting, treat the whole backend as one pool
|
||||
# XXX FIXME if multipool support is added to LVM driver.
|
||||
single_pool = {}
|
||||
single_pool.update(dict(
|
||||
pool_name=data["volume_backend_name"],
|
||||
total_capacity_gb=total_capacity,
|
||||
free_capacity_gb=free_capacity,
|
||||
reserved_percentage=self.configuration.reserved_percentage,
|
||||
location_info=location_info,
|
||||
QoS_support=False,
|
||||
))
|
||||
data["pools"].append(single_pool)
|
||||
|
||||
self._stats = data
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Verify that requirements are in place to use LVM driver."""
|
||||
if self.vg is None:
|
||||
root_helper = utils.get_root_helper()
|
||||
try:
|
||||
self.vg = lvm.LVM(self.configuration.volume_group,
|
||||
root_helper,
|
||||
lvm_type=self.configuration.lvm_type,
|
||||
executor=self._execute)
|
||||
except brick_exception.VolumeGroupNotFound:
|
||||
message = (_("Volume Group %s does not exist") %
|
||||
self.configuration.volume_group)
|
||||
raise exception.VolumeBackendAPIException(data=message)
|
||||
|
||||
vg_list = volutils.get_all_volume_groups(
|
||||
self.configuration.volume_group)
|
||||
vg_dict = \
|
||||
(vg for vg in vg_list if vg['name'] == self.vg.vg_name).next()
|
||||
if vg_dict is None:
|
||||
message = (_("Volume Group %s does not exist") %
|
||||
self.configuration.volume_group)
|
||||
raise exception.VolumeBackendAPIException(data=message)
|
||||
|
||||
if self.configuration.lvm_type == 'thin':
|
||||
# Specific checks for using Thin provisioned LV's
|
||||
if not volutils.supports_thin_provisioning():
|
||||
message = _("Thin provisioning not supported "
|
||||
"on this version of LVM.")
|
||||
raise exception.VolumeBackendAPIException(data=message)
|
||||
|
||||
pool_name = "%s-pool" % self.configuration.volume_group
|
||||
if self.vg.get_volume(pool_name) is None:
|
||||
try:
|
||||
self.vg.create_thin_pool(pool_name)
|
||||
except processutils.ProcessExecutionError as exc:
|
||||
exception_message = (_("Failed to create thin pool, "
|
||||
"error message was: %s")
|
||||
% exc.stderr)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exception_message)
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Creates a logical volume."""
|
||||
mirror_count = 0
|
||||
|
@ -300,7 +377,6 @@ class LVMVolumeDriver(driver.VolumeDriver):
|
|||
mirror_count)
|
||||
|
||||
self.vg.activate_lv(temp_snapshot['name'], is_snapshot=True)
|
||||
|
||||
volutils.copy_volume(
|
||||
self.local_path(temp_snapshot),
|
||||
self.local_path(volume),
|
||||
|
@ -339,65 +415,6 @@ class LVMVolumeDriver(driver.VolumeDriver):
|
|||
|
||||
return self._stats
|
||||
|
||||
def _update_volume_stats(self):
|
||||
"""Retrieve stats info from volume group."""
|
||||
|
||||
LOG.debug("Updating volume stats")
|
||||
if self.vg is None:
|
||||
LOG.warning(_LW('Unable to update stats on non-initialized '
|
||||
'Volume Group: %s'), self.configuration.
|
||||
volume_group)
|
||||
return
|
||||
|
||||
self.vg.update_volume_group_info()
|
||||
data = {}
|
||||
|
||||
# Note(zhiteng): These information are driver/backend specific,
|
||||
# each driver may define these values in its own config options
|
||||
# or fetch from driver specific configuration file.
|
||||
data["volume_backend_name"] = self.backend_name
|
||||
data["vendor_name"] = 'Open Source'
|
||||
data["driver_version"] = self.VERSION
|
||||
data["storage_protocol"] = self.protocol
|
||||
data["pools"] = []
|
||||
|
||||
total_capacity = 0
|
||||
free_capacity = 0
|
||||
if self.configuration.lvm_mirrors > 0:
|
||||
total_capacity = \
|
||||
self.vg.vg_mirror_size(self.configuration.lvm_mirrors)
|
||||
free_capacity = \
|
||||
self.vg.vg_mirror_free_space(self.configuration.lvm_mirrors)
|
||||
elif self.configuration.lvm_type == 'thin':
|
||||
total_capacity = self.vg.vg_thin_pool_size
|
||||
free_capacity = self.vg.vg_thin_pool_free_space
|
||||
else:
|
||||
total_capacity = self.vg.vg_size
|
||||
free_capacity = self.vg.vg_free_space
|
||||
|
||||
location_info = \
|
||||
('LVMVolumeDriver:%(hostname)s:%(vg)s'
|
||||
':%(lvm_type)s:%(lvm_mirrors)s' %
|
||||
{'hostname': self.hostname,
|
||||
'vg': self.configuration.volume_group,
|
||||
'lvm_type': self.configuration.lvm_type,
|
||||
'lvm_mirrors': self.configuration.lvm_mirrors})
|
||||
|
||||
# Skip enabled_pools setting, treat the whole backend as one pool
|
||||
# XXX FIXME if multipool support is added to LVM driver.
|
||||
single_pool = {}
|
||||
single_pool.update(dict(
|
||||
pool_name=data["volume_backend_name"],
|
||||
total_capacity_gb=total_capacity,
|
||||
free_capacity_gb=free_capacity,
|
||||
reserved_percentage=self.configuration.reserved_percentage,
|
||||
location_info=location_info,
|
||||
QoS_support=False,
|
||||
))
|
||||
data["pools"].append(single_pool)
|
||||
|
||||
self._stats = data
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend an existing volume's size."""
|
||||
self.vg.extend_volume(volume['name'],
|
||||
|
@ -458,130 +475,6 @@ class LVMVolumeDriver(driver.VolumeDriver):
|
|||
data=exception_message)
|
||||
return lv_size
|
||||
|
||||
def get_pool(self, volume):
|
||||
return self.backend_name
|
||||
|
||||
|
||||
class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
|
||||
"""Executes commands relating to ISCSI volumes.
|
||||
|
||||
We make use of model provider properties as follows:
|
||||
|
||||
``provider_location``
|
||||
if present, contains the iSCSI target information in the same
|
||||
format as an ietadm discovery
|
||||
i.e. '<ip>:<port>,<portal> <target IQN>'
|
||||
|
||||
``provider_auth``
|
||||
if present, contains a space-separated triple:
|
||||
'<auth method> <auth username> <auth password>'.
|
||||
`CHAP` is the only auth_method in use at the moment.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.db = kwargs.get('db')
|
||||
self.target_helper = self.get_target_helper(self.db)
|
||||
super(LVMISCSIDriver, self).__init__(*args, **kwargs)
|
||||
self.backend_name =\
|
||||
self.configuration.safe_get('volume_backend_name') or 'LVM_iSCSI'
|
||||
self.protocol = 'iSCSI'
|
||||
|
||||
def set_execute(self, execute):
|
||||
super(LVMISCSIDriver, self).set_execute(execute)
|
||||
if self.target_helper is not None:
|
||||
self.target_helper.set_execute(execute)
|
||||
|
||||
def _create_target(self, iscsi_name, iscsi_target,
|
||||
volume_path, chap_auth, lun=0,
|
||||
check_exit_code=False, old_name=None):
|
||||
# NOTE(jdg): tgt driver has an issue where with a lot of activity
|
||||
# (or sometimes just randomly) it will get *confused* and attempt
|
||||
# to reuse a target ID, resulting in a target already exists error
|
||||
# Typically a simple retry will address this
|
||||
|
||||
# For now we have this while loop, might be useful in the
|
||||
# future to throw a retry decorator in common or utils
|
||||
attempts = 2
|
||||
while attempts > 0:
|
||||
attempts -= 1
|
||||
try:
|
||||
# NOTE(jdg): For TgtAdm case iscsi_name is all we need
|
||||
# should clean this all up at some point in the future
|
||||
|
||||
tid = self.target_helper.create_iscsi_target(
|
||||
iscsi_name,
|
||||
iscsi_target,
|
||||
0,
|
||||
volume_path,
|
||||
chap_auth,
|
||||
check_exit_code=check_exit_code,
|
||||
old_name=old_name)
|
||||
break
|
||||
|
||||
except brick_exception.ISCSITargetCreateFailed:
|
||||
if attempts == 0:
|
||||
raise
|
||||
else:
|
||||
LOG.warning(_LW('Error creating iSCSI target, retrying '
|
||||
'creation for target: %s') % iscsi_name)
|
||||
return tid
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
volume_name = volume['name']
|
||||
iscsi_name = "%s%s" % (self.configuration.iscsi_target_prefix,
|
||||
volume_name)
|
||||
volume_path = "/dev/%s/%s" % (self.configuration.volume_group,
|
||||
volume_name)
|
||||
# NOTE(jdg): For TgtAdm case iscsi_name is the ONLY param we need
|
||||
# should clean this all up at some point in the future
|
||||
model_update = self.target_helper.ensure_export(
|
||||
context, volume,
|
||||
iscsi_name,
|
||||
volume_path,
|
||||
self.configuration.volume_group,
|
||||
self.configuration)
|
||||
if model_update:
|
||||
self.db.volume_update(context, volume['id'], model_update)
|
||||
|
||||
def create_export(self, context, volume):
|
||||
return self._create_export(context, volume)
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Initializes the connection and returns connection info. """
|
||||
|
||||
# We have a special case for lioadm here, that's fine, we can
|
||||
# keep the call in the parent class (driver:ISCSIDriver) generic
|
||||
# and still use it throughout, just override and call super here
|
||||
# no duplication, same effect but doesn't break things
|
||||
# see bug: #1400804
|
||||
if self.configuration.iscsi_helper == 'lioadm':
|
||||
self.target_helper.initialize_connection(volume, connector)
|
||||
return super(LVMISCSIDriver, self).initialize_connection(volume,
|
||||
connector)
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
if self.configuration.iscsi_helper == 'lioadm':
|
||||
self.target_helper.terminate_connection(volume, connector)
|
||||
|
||||
def _create_export(self, context, volume, vg=None):
|
||||
"""Creates an export for a logical volume."""
|
||||
if vg is None:
|
||||
vg = self.configuration.volume_group
|
||||
|
||||
volume_path = "/dev/%s/%s" % (vg, volume['name'])
|
||||
|
||||
data = self.target_helper.create_export(context,
|
||||
volume,
|
||||
volume_path,
|
||||
self.configuration)
|
||||
return {
|
||||
'provider_location': data['location'],
|
||||
'provider_auth': data['auth'],
|
||||
}
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
self.target_helper.remove_export(context, volume)
|
||||
|
||||
def migrate_volume(self, ctxt, volume, host, thin=False, mirror_count=0):
|
||||
"""Optimize the migration if the destination is on the same server.
|
||||
|
||||
|
@ -610,7 +503,7 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
|
|||
try:
|
||||
(vg for vg in vg_list if vg['name'] == dest_vg).next()
|
||||
except StopIteration:
|
||||
message = (_("Destination Volume Group %s does not exist") %
|
||||
message = (_LE("Destination Volume Group %s does not exist") %
|
||||
dest_vg)
|
||||
LOG.error(message)
|
||||
return false_ret
|
||||
|
@ -632,44 +525,93 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver):
|
|||
self.configuration.volume_dd_blocksize,
|
||||
execute=self._execute)
|
||||
self._delete_volume(volume)
|
||||
model_update = self._create_export(ctxt, volume, vg=dest_vg)
|
||||
model_update = self.create_export(ctxt, volume, vg=dest_vg)
|
||||
|
||||
return (True, model_update)
|
||||
else:
|
||||
message = (_("Refusing to migrate volume ID: %(id)s. Please "
|
||||
"check your configuration because source and "
|
||||
"destination are the same Volume Group: %(name)s.")
|
||||
% {'id': volume['id'], 'name': self.vg.vg_name})
|
||||
"destination are the same Volume Group: %(name)s."),
|
||||
{'id': volume['id'], 'name': self.vg.vg_name})
|
||||
LOG.exception(message)
|
||||
raise exception.VolumeBackendAPIException(data=message)
|
||||
|
||||
def _iscsi_location(self, ip, target, iqn, lun=None):
|
||||
return "%s:%s,%s %s %s" % (ip, self.configuration.iscsi_port,
|
||||
target, iqn, lun)
|
||||
def get_pool(self, volume):
|
||||
return self.backend_name
|
||||
|
||||
def _iscsi_authentication(self, chap, name, password):
|
||||
return "%s %s %s" % (chap, name, password)
|
||||
# ####### Interface methods for DataPath (Target Driver) ########
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
volume_name = volume['name']
|
||||
volume_path = "/dev/%s/%s" % (self.configuration.volume_group,
|
||||
volume_name)
|
||||
model_update = \
|
||||
self.target_driver.ensure_export(context,
|
||||
volume,
|
||||
volume_path=volume_path)
|
||||
return model_update
|
||||
|
||||
def create_export(self, context, volume, vg=None):
|
||||
if vg is None:
|
||||
vg = self.configuration.volume_group
|
||||
|
||||
volume_path = "/dev/%s/%s" % (vg, volume['name'])
|
||||
export_info = self.target_driver.create_export(context,
|
||||
volume,
|
||||
volume_path)
|
||||
return {'provider_location': export_info['location'],
|
||||
'provider_auth': export_info['auth'], }
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
self.target_driver.remove_export(context, volume)
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
return self.target_driver.initialize_connection(volume, connector)
|
||||
|
||||
def validate_connector(self, connector):
|
||||
return self.target_driver.validate_connector(connector)
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class LVMISERDriver(LVMISCSIDriver, driver.ISERDriver):
|
||||
"""Executes commands relating to ISER volumes.
|
||||
class LVMISCSIDriver(LVMVolumeDriver):
|
||||
"""Empty class designation for LVMISCSI.
|
||||
|
||||
We make use of model provider properties as follows:
|
||||
Since we've decoupled the inheritance of iSCSI and LVM we
|
||||
don't really need this class any longer. We do however want
|
||||
to keep it (at least for now) for back compat in driver naming.
|
||||
|
||||
``provider_location``
|
||||
if present, contains the iSER target information in the same
|
||||
format as an ietadm discovery
|
||||
i.e. '<ip>:<port>,<portal> <target IQN>'
|
||||
|
||||
``provider_auth``
|
||||
if present, contains a space-separated triple:
|
||||
'<auth method> <auth username> <auth password>'.
|
||||
`CHAP` is the only auth_method in use at the moment.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.target_helper = self.get_target_helper(kwargs.get('db'))
|
||||
LVMVolumeDriver.__init__(self, *args, **kwargs)
|
||||
self.backend_name =\
|
||||
self.configuration.safe_get('volume_backend_name') or 'LVM_iSER'
|
||||
self.protocol = 'iSER'
|
||||
super(LVMISCSIDriver, self).__init__(*args, **kwargs)
|
||||
LOG.warning(_LW('LVMISCSIDriver is deprecated, you should '
|
||||
'now just use LVMVolumeDriver and specify '
|
||||
'target_helper for the target driver you '
|
||||
'wish to use.'))
|
||||
|
||||
|
||||
class LVMISERDriver(LVMVolumeDriver):
|
||||
"""Empty class designation for LVMISER.
|
||||
|
||||
Since we've decoupled the inheritance of data path in LVM we
|
||||
don't really need this class any longer. We do however want
|
||||
to keep it (at least for now) for back compat in driver naming.
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(LVMISERDriver, self).__init__(*args, **kwargs)
|
||||
|
||||
LOG.warning(_LW('LVMISCSIDriver is deprecated, you should '
|
||||
'now just use LVMVolumeDriver and specify '
|
||||
'target_helper for the target driver you '
|
||||
'wish to use.'))
|
||||
|
||||
LOG.debug('Attempting to initialize LVM driver with the '
|
||||
'following target_driver: '
|
||||
'cinder.volume.targets.iser.ISERTgtAdm')
|
||||
self.target_driver = importutils.import_object(
|
||||
'cinder.volume.targets.iser.ISERTgtAdm',
|
||||
configuration=self.configuration,
|
||||
db=self.db,
|
||||
executor=self._execute)
|
||||
|
|
|
@ -811,7 +811,8 @@ class SRBISCSIDriver(SRBDriver, driver.ISCSIDriver):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.db = kwargs.get('db')
|
||||
self.target_helper = self.get_target_helper(self.db)
|
||||
self.target_driver = \
|
||||
self.target_mapping[self.configuration.safe_get('iscsi_helper')]
|
||||
super(SRBISCSIDriver, self).__init__(*args, **kwargs)
|
||||
self.backend_name =\
|
||||
self.configuration.safe_get('volume_backend_name') or 'SRB_iSCSI'
|
||||
|
@ -819,8 +820,8 @@ class SRBISCSIDriver(SRBDriver, driver.ISCSIDriver):
|
|||
|
||||
def set_execute(self, execute):
|
||||
super(SRBISCSIDriver, self).set_execute(execute)
|
||||
if self.target_helper is not None:
|
||||
self.target_helper.set_execute(execute)
|
||||
if self.target_driver is not None:
|
||||
self.target_driver.set_execute(execute)
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
volume_name = volume['name']
|
||||
|
@ -829,7 +830,7 @@ class SRBISCSIDriver(SRBDriver, driver.ISCSIDriver):
|
|||
device_path = self._mapper_path(volume)
|
||||
# NOTE(jdg): For TgtAdm case iscsi_name is the ONLY param we need
|
||||
# should clean this all up at some point in the future
|
||||
model_update = self.target_helper.ensure_export(
|
||||
model_update = self.target_driver.ensure_export(
|
||||
context, volume,
|
||||
iscsi_name,
|
||||
device_path,
|
||||
|
@ -847,7 +848,7 @@ class SRBISCSIDriver(SRBDriver, driver.ISCSIDriver):
|
|||
# SRB uses the same name as the volume for the VG
|
||||
volume_path = self._mapper_path(volume)
|
||||
|
||||
data = self.target_helper.create_export(context,
|
||||
data = self.target_driver.create_export(context,
|
||||
volume,
|
||||
volume_path,
|
||||
self.configuration)
|
||||
|
@ -862,14 +863,14 @@ class SRBISCSIDriver(SRBDriver, driver.ISCSIDriver):
|
|||
# an export, and avoid screwing up the device attach refcount.
|
||||
try:
|
||||
# Raises exception.NotFound if export not provisioned
|
||||
iscsi_target = self.target_helper._get_iscsi_target(context,
|
||||
iscsi_target = self.target_driver._get_iscsi_target(context,
|
||||
volume['id'])
|
||||
# Raises an Exception if currently not exported
|
||||
location = volume['provider_location'].split(' ')
|
||||
iqn = location[1]
|
||||
self.target_helper.show_target(iscsi_target, iqn=iqn)
|
||||
self.target_driver.show_target(iscsi_target, iqn=iqn)
|
||||
|
||||
self.target_helper.remove_export(context, volume)
|
||||
self.target_driver.remove_export(context, volume)
|
||||
self._detach_file(volume)
|
||||
except exception.NotFound:
|
||||
LOG.warning(_LW('Volume %r not found while trying to remove.'),
|
||||
|
|
|
@ -1,291 +0,0 @@
|
|||
# Copyright (c) 2013 Mirantis, Inc.
|
||||
# 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.
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from oslo_concurrency import processutils as putils
|
||||
|
||||
from cinder.brick.iscsi import iscsi
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.volume import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _ExportMixin(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.db = kwargs.pop('db', None)
|
||||
super(_ExportMixin, self).__init__(*args, **kwargs)
|
||||
|
||||
def create_export(self, context, volume, volume_path, conf):
|
||||
"""Creates an export for a logical volume."""
|
||||
iscsi_name = "%s%s" % (conf.iscsi_target_prefix,
|
||||
volume['name'])
|
||||
max_targets = conf.safe_get('iscsi_num_targets')
|
||||
(iscsi_target, lun) = self._get_target_and_lun(context,
|
||||
volume,
|
||||
max_targets)
|
||||
|
||||
# Verify we haven't setup a CHAP creds file already
|
||||
# if DNE no big deal, we'll just create it
|
||||
current_chap_auth = self._get_target_chap_auth(iscsi_name)
|
||||
if current_chap_auth:
|
||||
(chap_username, chap_password) = current_chap_auth
|
||||
else:
|
||||
chap_username = utils.generate_username()
|
||||
chap_password = utils.generate_password()
|
||||
chap_auth = self._iscsi_authentication('IncomingUser',
|
||||
chap_username,
|
||||
chap_password)
|
||||
# NOTE(jdg): For TgtAdm case iscsi_name is the ONLY param we need
|
||||
# should clean this all up at some point in the future
|
||||
tid = self.create_iscsi_target(iscsi_name,
|
||||
iscsi_target,
|
||||
0,
|
||||
volume_path,
|
||||
chap_auth,
|
||||
write_cache=
|
||||
conf.iscsi_write_cache)
|
||||
data = {}
|
||||
data['location'] = self._iscsi_location(
|
||||
conf.iscsi_ip_address, tid, iscsi_name, conf.iscsi_port, lun)
|
||||
|
||||
LOG.debug('Set provider_location to: %s', data['location'])
|
||||
data['auth'] = self._iscsi_authentication(
|
||||
'CHAP', chap_username, chap_password)
|
||||
return data
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
try:
|
||||
iscsi_target = self._get_iscsi_target(context, volume['id'])
|
||||
except exception.NotFound:
|
||||
LOG.info(_LI("Skipping remove_export. No iscsi_target "
|
||||
"provisioned for volume: %s"), volume['id'])
|
||||
return
|
||||
try:
|
||||
|
||||
# NOTE: provider_location may be unset if the volume hasn't
|
||||
# been exported
|
||||
location = volume['provider_location'].split(' ')
|
||||
iqn = location[1]
|
||||
|
||||
# ietadm show will exit with an error
|
||||
# this export has already been removed
|
||||
self.show_target(iscsi_target, iqn=iqn)
|
||||
|
||||
except Exception:
|
||||
LOG.info(_LI("Skipping remove_export. No iscsi_target "
|
||||
"is presently exported for volume: %s"), volume['id'])
|
||||
return
|
||||
|
||||
self.remove_iscsi_target(iscsi_target, 0, volume['id'], volume['name'])
|
||||
|
||||
def ensure_export(self, context, volume, iscsi_name, volume_path,
|
||||
vg_name, conf, old_name=None):
|
||||
iscsi_target = self._get_target_for_ensure_export(context,
|
||||
volume['id'])
|
||||
if iscsi_target is None:
|
||||
LOG.info(_LI("Skipping remove_export. No iscsi_target "
|
||||
"provisioned for volume: %s"), volume['id'])
|
||||
return
|
||||
chap_auth = None
|
||||
# Check for https://bugs.launchpad.net/cinder/+bug/1065702
|
||||
old_name = None
|
||||
if (volume['provider_location'] is not None and
|
||||
volume['name'] not in volume['provider_location']):
|
||||
|
||||
msg = _('Detected inconsistency in provider_location id')
|
||||
LOG.debug('%s', msg)
|
||||
old_name = self._fix_id_migration(context, volume, vg_name)
|
||||
if 'in-use' in volume['status']:
|
||||
old_name = None
|
||||
self.create_iscsi_target(iscsi_name, iscsi_target, 0, volume_path,
|
||||
chap_auth, check_exit_code=False,
|
||||
old_name=old_name,
|
||||
write_cache=conf.iscsi_write_cache)
|
||||
|
||||
def _ensure_iscsi_targets(self, context, host, max_targets):
|
||||
"""Ensure that target ids have been created in datastore."""
|
||||
# NOTE(jdg): tgtadm doesn't use the iscsi_targets table
|
||||
# TODO(jdg): In the future move all of the dependent stuff into the
|
||||
# cooresponding target admin class
|
||||
host_iscsi_targets = self.db.iscsi_target_count_by_host(context,
|
||||
host)
|
||||
if host_iscsi_targets >= max_targets:
|
||||
return
|
||||
|
||||
# NOTE(vish): Target ids start at 1, not 0.
|
||||
target_end = max_targets + 1
|
||||
for target_num in xrange(1, target_end):
|
||||
target = {'host': host, 'target_num': target_num}
|
||||
self.db.iscsi_target_create_safe(context, target)
|
||||
|
||||
def _get_target_for_ensure_export(self, context, volume_id):
|
||||
try:
|
||||
iscsi_target = self.db.volume_get_iscsi_target_num(context,
|
||||
volume_id)
|
||||
return iscsi_target
|
||||
except exception.NotFound:
|
||||
return None
|
||||
|
||||
def _get_target_and_lun(self, context, volume, max_targets):
|
||||
lun = 0
|
||||
self._ensure_iscsi_targets(context, volume['host'], max_targets)
|
||||
iscsi_target = self.db.volume_allocate_iscsi_target(context,
|
||||
volume['id'],
|
||||
volume['host'])
|
||||
return iscsi_target, lun
|
||||
|
||||
def _get_iscsi_target(self, context, vol_id):
|
||||
return self.db.volume_get_iscsi_target_num(context, vol_id)
|
||||
|
||||
def _iscsi_authentication(self, chap, name, password):
|
||||
return "%s %s %s" % (chap, name, password)
|
||||
|
||||
def _iscsi_location(self, ip, target, iqn, port, lun=None):
|
||||
return "%s:%s,%s %s %s" % (ip, port,
|
||||
target, iqn, lun)
|
||||
|
||||
def _fix_id_migration(self, context, volume, vg_name):
|
||||
"""Fix provider_location and dev files to address bug 1065702.
|
||||
|
||||
For volumes that the provider_location has NOT been updated
|
||||
and are not currently in-use we'll create a new iscsi target
|
||||
and remove the persist file.
|
||||
|
||||
If the volume is in-use, we'll just stick with the old name
|
||||
and when detach is called we'll feed back into ensure_export
|
||||
again if necessary and fix things up then.
|
||||
|
||||
Details at: https://bugs.launchpad.net/cinder/+bug/1065702
|
||||
"""
|
||||
|
||||
model_update = {}
|
||||
pattern = re.compile(r":|\s")
|
||||
fields = pattern.split(volume['provider_location'])
|
||||
old_name = fields[3]
|
||||
|
||||
volume['provider_location'] = \
|
||||
volume['provider_location'].replace(old_name, volume['name'])
|
||||
model_update['provider_location'] = volume['provider_location']
|
||||
|
||||
self.db.volume_update(context, volume['id'], model_update)
|
||||
|
||||
start = os.getcwd()
|
||||
|
||||
os.chdir('/dev/%s' % vg_name)
|
||||
|
||||
try:
|
||||
(out, err) = self._execute('readlink', old_name)
|
||||
except putils.ProcessExecutionError:
|
||||
link_path = '/dev/%s/%s' % (vg_name,
|
||||
old_name)
|
||||
LOG.debug('Symbolic link %s not found' % link_path)
|
||||
os.chdir(start)
|
||||
return
|
||||
|
||||
rel_path = out.rstrip()
|
||||
self._execute('ln',
|
||||
'-s',
|
||||
rel_path, volume['name'],
|
||||
run_as_root=True)
|
||||
os.chdir(start)
|
||||
return old_name
|
||||
|
||||
|
||||
class TgtAdm(_ExportMixin, iscsi.TgtAdm):
|
||||
|
||||
def _get_target_and_lun(self, context, volume, max_targets):
|
||||
lun = 1 # For tgtadm the controller is lun 0, dev starts at lun 1
|
||||
iscsi_target = 0 # NOTE(jdg): Not used by tgtadm
|
||||
return iscsi_target, lun
|
||||
|
||||
def _get_iscsi_target(self, context, vol_id):
|
||||
return 0
|
||||
|
||||
def _get_target_for_ensure_export(self, context, volume_id):
|
||||
return 1
|
||||
|
||||
|
||||
class FakeIscsiHelper(_ExportMixin, iscsi.FakeIscsiHelper):
|
||||
|
||||
def create_export(self, context, volume, volume_path, conf):
|
||||
return {
|
||||
'location': "fake_location",
|
||||
'auth': "fake_auth"
|
||||
}
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
pass
|
||||
|
||||
def ensure_export(self, context, volume, iscsi_name, volume_path,
|
||||
vg_name, conf, old_name=None):
|
||||
pass
|
||||
|
||||
|
||||
class LioAdm(_ExportMixin, iscsi.LioAdm):
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
try:
|
||||
iscsi_target = self.db.volume_get_iscsi_target_num(context,
|
||||
volume['id'])
|
||||
except exception.NotFound:
|
||||
LOG.info(_LI("Skipping remove_export. No iscsi_target "
|
||||
"provisioned for volume: %s"), volume['id'])
|
||||
return
|
||||
|
||||
self.remove_iscsi_target(iscsi_target, 0, volume['id'], volume['name'])
|
||||
|
||||
def ensure_export(self, context, volume, iscsi_name, volume_path,
|
||||
vg_name, conf, old_name=None):
|
||||
try:
|
||||
volume_info = self.db.volume_get(context, volume['id'])
|
||||
except exception.NotFound:
|
||||
LOG.info(_LI("Skipping ensure_export. No iscsi_target "
|
||||
"provision for volume: %s"), volume['id'])
|
||||
return
|
||||
|
||||
(auth_method,
|
||||
auth_user,
|
||||
auth_pass) = volume_info['provider_auth'].split(' ', 3)
|
||||
chap_auth = self._iscsi_authentication(auth_method,
|
||||
auth_user,
|
||||
auth_pass)
|
||||
|
||||
iscsi_target = 1
|
||||
|
||||
self.create_iscsi_target(iscsi_name, iscsi_target, 0, volume_path,
|
||||
chap_auth, check_exit_code=False)
|
||||
|
||||
|
||||
class IetAdm(_ExportMixin, iscsi.IetAdm):
|
||||
pass
|
||||
|
||||
|
||||
class ISERTgtAdm(_ExportMixin, iscsi.ISERTgtAdm):
|
||||
def _get_target_and_lun(self, context, volume, max_targets):
|
||||
lun = 1 # For tgtadm the controller is lun 0, dev starts at lun 1
|
||||
iscsi_target = 0 # NOTE(jdg): Not used by tgtadm
|
||||
return iscsi_target, lun
|
||||
|
||||
def _get_iscsi_target(self, context, vol_id):
|
||||
return 0
|
||||
|
||||
def _get_target_for_ensure_export(self, context, volume_id):
|
||||
return 1
|
|
@ -44,7 +44,7 @@ class Target(object):
|
|||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_export(self, context, volume):
|
||||
def create_export(self, context, volume, volume_path):
|
||||
"""Exports a Target/Volume.
|
||||
|
||||
Can optionally return a Dict of changes to
|
||||
|
|
|
@ -10,8 +10,10 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cinder.volume.targets import iscsi
|
||||
|
||||
class FakeTarget(object):
|
||||
|
||||
class FakeTarget(iscsi.ISCSITarget):
|
||||
VERSION = '0.1'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -22,8 +24,11 @@ class FakeTarget(object):
|
|||
volume_group, config):
|
||||
pass
|
||||
|
||||
def create_export(self, context, volume):
|
||||
pass
|
||||
def create_export(self, context, volume, volume_path):
|
||||
return {
|
||||
'location': "fake_location",
|
||||
'auth': "fake_auth"
|
||||
}
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
pass
|
||||
|
|
|
@ -22,7 +22,7 @@ class IetAdm(object):
|
|||
volume_group, config):
|
||||
pass
|
||||
|
||||
def create_export(self, context, volume):
|
||||
def create_export(self, context, volume, volume_path):
|
||||
pass
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslo.concurrency import processutils
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LW, _LE
|
||||
|
|
Loading…
Reference in New Issue