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:
John Griffith 2014-11-18 00:46:54 +00:00
parent 1283b4c6a8
commit 9651f54714
17 changed files with 403 additions and 1628 deletions

View File

@ -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)

View File

@ -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'])

View File

@ -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

View File

@ -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,

View File

@ -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')

View File

@ -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',

View File

@ -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)

View File

@ -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."""

View File

@ -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

View File

@ -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)

View File

@ -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.'),

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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