From: Ann Kamyshnikova Date: Tue, 27 Aug 2013 12:17:25 +0000 (+0400) Subject: Move create_, remove_ and ensure_export from drivers X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=bdf6e2b5f853b0608dfb81b0d0489b469ead5fd2;p=openstack-build%2Fcinder-build.git Move create_, remove_ and ensure_export from drivers create_export, remove_export, ensure_export are almost the same in LVM and BlockDevice drivers. So they should be moved to a common cinder.volume.iscsi module. Change-Id: Id7aa0e10d6b346356312416aee179a1fe8bacef1 --- diff --git a/cinder/tests/test_block_device.py b/cinder/tests/test_block_device.py index c96156dcc..7def672fd 100644 --- a/cinder/tests/test_block_device.py +++ b/cinder/tests/test_block_device.py @@ -102,8 +102,7 @@ class TestBlockDeviceDriver(cinder.test.TestCase): self.mox.ReplayAll() result = self.drv.create_volume(TEST_VOLUME) self.assertEqual(result, { - 'provider_location': 'None:3260,None None ' - 'None dev_path'}) + 'provider_location': 'dev_path'}) def test_update_volume_stats(self): self.mox.StubOutWithMock(self.drv, '_devices_sizes') @@ -145,8 +144,7 @@ class TestBlockDeviceDriver(cinder.test.TestCase): execute=self.drv._execute) self.mox.ReplayAll() self.assertEqual(self.drv.create_cloned_volume(TEST_VOLUME, TEST_SRC), - {'provider_location': 'None:3260,' - 'None None None /dev/loop2'}) + {'provider_location': '/dev/loop2'}) def test_copy_image_to_volume(self): TEST_VOLUME = {'provider_location': '1 2 3 /dev/loop1', 'size': 1} diff --git a/cinder/tests/test_iscsi.py b/cinder/tests/test_iscsi.py index 99e15887d..87bb1c6e0 100644 --- a/cinder/tests/test_iscsi.py +++ b/cinder/tests/test_iscsi.py @@ -34,6 +34,7 @@ class TargetAdminTestCase(object): self.path = '/foo' self.vol_id = 'blaa' self.vol_name = 'volume-blaa' + self.db = {} self.script_template = None self.stubs.Set(os.path, 'isfile', lambda _: True) @@ -89,7 +90,7 @@ class TargetAdminTestCase(object): self.verify_cmds(cmds) def run_commands(self): - target_helper = self.driver.get_target_helper() + target_helper = self.driver.get_target_helper(self.db) target_helper.set_execute(self.fake_execute) target_helper.create_iscsi_target(self.target_name, self.tid, self.lun, self.path) diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py index 544f81bf6..cd96acc1e 100644 --- a/cinder/volume/driver.py +++ b/cinder/volume/driver.py @@ -22,7 +22,6 @@ import time from oslo.config import cfg -from cinder.brick.iscsi import iscsi from cinder import exception from cinder.image import image_utils from cinder.openstack.common import excutils @@ -30,6 +29,7 @@ from cinder.openstack.common import fileutils from cinder.openstack.common import log as logging from cinder.openstack.common import processutils 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 @@ -739,23 +739,25 @@ class ISCSIDriver(VolumeDriver): data['QoS_support'] = False self._stats = data - def get_target_helper(self): + def get_target_helper(self, db): root_helper = utils.get_root_helper() if CONF.iscsi_helper == 'iseradm': return iscsi.ISERTgtAdm(root_helper, CONF.volumes_dir, - CONF.iscsi_target_prefix) + CONF.iscsi_target_prefix, db=db) elif CONF.iscsi_helper == 'tgtadm': return iscsi.TgtAdm(root_helper, CONF.volumes_dir, - CONF.iscsi_target_prefix) + 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.lio_initiator_iqns, - CONF.iscsi_target_prefix) + CONF.iscsi_target_prefix, db=db) else: - return iscsi.IetAdm(root_helper, CONF.iet_conf, CONF.iscsi_iotype) + return iscsi.IetAdm(root_helper, CONF.iet_conf, CONF.iscsi_iotype, + db=db) class FakeISCSIDriver(ISCSIDriver): @@ -858,14 +860,14 @@ class ISERDriver(ISCSIDriver): data['QoS_support'] = False self._stats = data - def get_target_helper(self): + 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) + CONF.volumes_dir, db=db) class FakeISERDriver(FakeISCSIDriver): diff --git a/cinder/volume/drivers/block_device.py b/cinder/volume/drivers/block_device.py index 2150cd326..e2ce8ef2d 100644 --- a/cinder/volume/drivers/block_device.py +++ b/cinder/volume/drivers/block_device.py @@ -17,13 +17,11 @@ import os from oslo.config import cfg -from cinder.brick.iscsi import iscsi from cinder import context from cinder.db.sqlalchemy import api from cinder import exception from cinder.image import image_utils from cinder.openstack.common import log as logging -from cinder import utils from cinder.volume import driver from cinder.volume import utils as volutils @@ -44,14 +42,14 @@ class BlockDeviceDriver(driver.ISCSIDriver): VERSION = '1.0.0' def __init__(self, *args, **kwargs): - self.target_helper = self.get_target_helper() - + 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) - self.target_helper.set_execute(execute) + if self.target_helper is not None: + self.target_helper.set_execute(execute) def check_for_setup_error(self): pass @@ -60,8 +58,7 @@ class BlockDeviceDriver(driver.ISCSIDriver): device = self.find_appropriate_size_device(volume['size']) LOG.info("Create %s on %s" % (volume['name'], device)) return { - 'provider_location': self._iscsi_location(None, None, None, None, - device), + 'provider_location': device, } def initialize_connection(self, volume, connector): @@ -79,136 +76,17 @@ class BlockDeviceDriver(driver.ISCSIDriver): def create_export(self, context, volume): """Creates an export for a logical volume.""" - - iscsi_name = "%s%s" % (self.configuration.iscsi_target_prefix, - volume['name']) volume_path = self.local_path(volume) - model_update = {} - - # TODO(jdg): In the future move all of the dependent stuff into the - # corresponding target admin class - if not isinstance(self.target_helper, iscsi.TgtAdm): - lun = 0 - self._ensure_iscsi_targets(context, volume['host']) - iscsi_target = self.db.volume_allocate_iscsi_target(context, - volume['id'], - volume['host']) - else: - lun = 1 # For tgtadm the controller is lun 0, dev starts at lun 1 - iscsi_target = 0 # NOTE(jdg): Not used by tgtadm - - # Use the same method to generate the username and the password. - 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.target_helper.create_iscsi_target(iscsi_name, - iscsi_target, - 0, - volume_path, - chap_auth) - model_update['provider_location'] = self._iscsi_location( - self.configuration.iscsi_ip_address, tid, iscsi_name, lun, - volume_path) - model_update['provider_auth'] = self._iscsi_authentication( - 'CHAP', chap_username, chap_password) - return model_update + data = self.target_helper.create_export(context, volume, volume_path) + return { + 'provider_location': data['location'] + ' ' + volume_path, + 'provider_auth': data['auth'], + } def remove_export(self, context, volume): - """Removes an export for a logical volume.""" - # NOTE(jdg): tgtadm doesn't use the iscsi_targets table - # TODO(jdg): In the future move all of the dependent stuff into the - # corresponding target admin class - - if isinstance(self.target_helper, iscsi.LioAdm): - try: - iscsi_target = self.db.volume_get_iscsi_target_num( - context, - volume['id']) - except exception.NotFound: - LOG.info(_("Skipping remove_export. No iscsi_target " - "provisioned for volume: %s"), volume['id']) - return - self.target_helper.remove_iscsi_target(iscsi_target, - 0, - volume['id'], - volume['name']) - return - elif not isinstance(self.target_helper, iscsi.TgtAdm): - try: - iscsi_target = self.db.volume_get_iscsi_target_num( - context, - volume['id']) - except exception.NotFound: - LOG.info(_("Skipping remove_export. No iscsi_target " - "provisioned for volume: %s"), volume['id']) - return - else: - iscsi_target = 0 - 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.target_helper.show_target(iscsi_target, iqn=iqn) - except Exception: - LOG.info(_("Skipping remove_export. No iscsi_target " - "is presently exported for volume: %s"), volume['id']) - return - self.target_helper.remove_iscsi_target(iscsi_target, 0, volume['id'], - volume['name']) + self.target_helper.remove_export(context, volume) def ensure_export(self, context, volume): - """Synchronously recreates an export for a logical volume. - :param context: - :param volume: - """ - # NOTE(jdg): tgtadm doesn't use the iscsi_targets table - # TODO(jdg): In the future move all of the dependent stuff into the - # corresponding target admin class - - if isinstance(self.target_helper, iscsi.LioAdm): - try: - volume_info = self.db.volume_get(context, volume['id']) - (auth_method, - auth_user, - auth_pass) = volume_info['provider_auth'].split(' ', 3) - chap_auth = self._iscsi_authentication(auth_method, - auth_user, - auth_pass) - except exception.NotFound: - LOG.debug("volume_info:", volume_info) - LOG.info(_("Skipping ensure_export. No iscsi_target " - "provision for volume: %s"), volume['id']) - return - iscsi_name = "%s%s" % (self.configuration.iscsi_target_prefix, - volume['name']) - volume_path = self.local_path(volume) - iscsi_target = 1 - self.target_helper.create_iscsi_target(iscsi_name, iscsi_target, - 0, volume_path, chap_auth, - check_exit_code=False) - return - if not isinstance(self.target_helper, iscsi.TgtAdm): - try: - iscsi_target = self.db.volume_get_iscsi_target_num( - context, - volume['id']) - except exception.NotFound: - LOG.info(_("Skipping ensure_export. No iscsi_target " - "provisioned for volume: %s"), volume['id']) - return - else: - iscsi_target = 1 # dummy value when using TgtAdm - - chap_auth = None - - # Check for https://bugs.launchpad.net/cinder/+bug/1065702 - old_name = None volume_name = volume['name'] iscsi_name = "%s%s" % (self.configuration.iscsi_target_prefix, @@ -217,34 +95,8 @@ class BlockDeviceDriver(driver.ISCSIDriver): # 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.create_iscsi_target(iscsi_name, iscsi_target, - 0, volume_path, chap_auth, - check_exit_code=False, - old_name=old_name) - - def _iscsi_location(self, ip, target, iqn, lun=None, device=None): - return "%s:%s,%s %s %s %s" % (ip, self.configuration.iscsi_port, - target, iqn, lun, device) - - def _iscsi_authentication(self, chap, name, password): - return "%s %s %s" % (chap, name, password) - - def _ensure_iscsi_targets(self, context, host): - """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 - # corresponding target admin class - if not isinstance(self.target_helper, iscsi.TgtAdm): - host_iscsi_targets = self.db.iscsi_target_count_by_host(context, - host) - if host_iscsi_targets >= self.configuration.iscsi_num_targets: - return - - # NOTE(vish): Target ids start at 1, not 0. - target_end = self.configuration.iscsi_num_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) + self.target_helper.ensure_export(context, volume, iscsi_name, + volume_path) def delete_volume(self, volume): """Deletes a logical volume.""" @@ -261,8 +113,8 @@ class BlockDeviceDriver(driver.ISCSIDriver): def local_path(self, volume): if volume['provider_location']: - path = volume['provider_location'].split(" ") - return path[3] + path = volume['provider_location'].rsplit(" ", 1) + return path[-1] else: return None @@ -291,8 +143,7 @@ class BlockDeviceDriver(driver.ISCSIDriver): self.configuration.volume_dd_blocksize, execute=self._execute) return { - 'provider_location': self._iscsi_location(None, None, None, None, - device), + 'provider_location': device, } def get_volume_stats(self, refresh=False): diff --git a/cinder/volume/drivers/lvm.py b/cinder/volume/drivers/lvm.py index b5054f9ea..fe7932888 100644 --- a/cinder/volume/drivers/lvm.py +++ b/cinder/volume/drivers/lvm.py @@ -19,13 +19,11 @@ Driver for Linux servers running LVM. """ import os -import re import socket from oslo.config import cfg from cinder.brick import exception as brick_exception -from cinder.brick.iscsi import iscsi from cinder.brick.local_dev import lvm as lvm from cinder import exception from cinder.image import image_utils @@ -406,7 +404,8 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver): """ def __init__(self, *args, **kwargs): - self.target_helper = self.get_target_helper() + 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' @@ -414,7 +413,8 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver): def set_execute(self, execute): super(LVMISCSIDriver, self).set_execute(execute) - self.target_helper.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, @@ -451,140 +451,18 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver): return tid def ensure_export(self, context, volume): - """Synchronously recreates an export for a logical volume.""" - # NOTE(jdg): tgtadm doesn't use the iscsi_targets table - # TODO(jdg): In the future move all of the dependent stuff into the - # corresponding target admin class - - if isinstance(self.target_helper, iscsi.LioAdm): - try: - volume_info = self.db.volume_get(context, volume['id']) - (auth_method, - auth_user, - auth_pass) = volume_info['provider_auth'].split(' ', 3) - chap_auth = self._iscsi_authentication(auth_method, - auth_user, - auth_pass) - except exception.NotFound: - LOG.debug(_("volume_info:%s"), volume_info) - LOG.info(_("Skipping ensure_export. No iscsi_target " - "provision for volume: %s"), volume['id']) - return - - iscsi_name = "%s%s" % (self.configuration.iscsi_target_prefix, - volume['name']) - volume_path = "/dev/%s/%s" % (self.configuration.volume_group, - volume['name']) - iscsi_target = 1 - - self._create_target(iscsi_name, iscsi_target, - volume_path, chap_auth) - - return - - if not isinstance(self.target_helper, iscsi.TgtAdm): - try: - iscsi_target = self.db.volume_get_iscsi_target_num( - context, - volume['id']) - except exception.NotFound: - LOG.info(_("Skipping ensure_export. No iscsi_target " - "provisioned for volume: %s"), volume['id']) - return - else: - iscsi_target = 1 # dummy value when using TgtAdm - - chap_auth = None - - # Check for https://bugs.launchpad.net/cinder/+bug/1065702 - old_name = None volume_name = volume['name'] - 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) - if 'in-use' in volume['status']: - volume_name = old_name - old_name = None - 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 - self._create_target(iscsi_name, iscsi_target, - volume_path, chap_auth, - lun=0, - check_exit_code=False, - old_name=old_name) - - return - - def _fix_id_migration(self, context, volume): - """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' % self.configuration.volume_group) - - try: - (out, err) = self._execute('readlink', old_name) - except processutils.ProcessExecutionError: - link_path = '/dev/%s/%s' % (self.configuration.volume_group, - 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 - - def _ensure_iscsi_targets(self, context, host): - """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 - # corresponding target admin class - if not isinstance(self.target_helper, iscsi.TgtAdm): - host_iscsi_targets = self.db.iscsi_target_count_by_host(context, - host) - if host_iscsi_targets >= self.configuration.iscsi_num_targets: - return - - # NOTE(vish): Target ids start at 1, not 0. - target_end = self.configuration.iscsi_num_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) + model_update = self.target_helper.ensure_export(context, volume, + iscsi_name, + volume_path) + if model_update: + self.db.volume_update(context, volume['id'], model_update) def create_export(self, context, volume): return self._create_export(context, volume) @@ -594,94 +472,16 @@ class LVMISCSIDriver(LVMVolumeDriver, driver.ISCSIDriver): if vg is None: vg = self.configuration.volume_group - iscsi_name = "%s%s" % (self.configuration.iscsi_target_prefix, - volume['name']) volume_path = "/dev/%s/%s" % (vg, volume['name']) - model_update = {} - - # TODO(jdg): In the future move all of the dependent stuff into the - # corresponding target admin class - if not isinstance(self.target_helper, iscsi.TgtAdm): - lun = 0 - self._ensure_iscsi_targets(context, volume['host']) - iscsi_target = self.db.volume_allocate_iscsi_target(context, - volume['id'], - volume['host']) - else: - lun = 1 # For tgtadm the controller is lun 0, dev starts at lun 1 - iscsi_target = 0 # NOTE(jdg): Not used by tgtadm - - # Use the same method to generate the username and the password. - chap_username = utils.generate_username() - chap_password = utils.generate_password() - chap_auth = self._iscsi_authentication('IncomingUser', chap_username, - chap_password) - tid = self._create_target(iscsi_name, iscsi_target, - volume_path, chap_auth) - - model_update['provider_location'] = self._iscsi_location( - self.configuration.iscsi_ip_address, tid, iscsi_name, lun) - model_update['provider_auth'] = self._iscsi_authentication( - 'CHAP', chap_username, chap_password) - return model_update + data = self.target_helper.create_export(context, volume, volume_path) + return { + 'provider_location': data['location'], + 'provider_auth': data['auth'], + } def remove_export(self, context, volume): - """Removes an export for a logical volume.""" - # NOTE(jdg): tgtadm doesn't use the iscsi_targets table - # TODO(jdg): In the future move all of the dependent stuff into the - # corresponding target admin class - - if isinstance(self.target_helper, iscsi.LioAdm): - try: - iscsi_target = self.db.volume_get_iscsi_target_num( - context, - volume['id']) - except exception.NotFound: - LOG.info(_("Skipping remove_export. No iscsi_target " - "provisioned for volume: %s"), volume['id']) - return - - self.target_helper.remove_iscsi_target(iscsi_target, - 0, - volume['id'], - volume['name']) - - return - - elif not isinstance(self.target_helper, iscsi.TgtAdm): - try: - iscsi_target = self.db.volume_get_iscsi_target_num( - context, - volume['id']) - except exception.NotFound: - LOG.info(_("Skipping remove_export. No iscsi_target " - "provisioned for volume: %s"), volume['id']) - return - - else: - iscsi_target = 0 - - 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.target_helper.show_target(iscsi_target, iqn=iqn) - - except Exception: - LOG.info(_("Skipping remove_export. No iscsi_target " - "is presently exported for volume: %s"), volume['id']) - return - - self.target_helper.remove_iscsi_target(iscsi_target, - 0, - volume['name_id'], - volume['name']) + 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. @@ -762,7 +562,7 @@ class LVMISERDriver(LVMISCSIDriver, driver.ISERDriver): """ def __init__(self, *args, **kwargs): - self.target_helper = self.get_target_helper() + 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' diff --git a/cinder/volume/iscsi.py b/cinder/volume/iscsi.py new file mode 100644 index 000000000..807b1c1ac --- /dev/null +++ b/cinder/volume/iscsi.py @@ -0,0 +1,266 @@ +# 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.config import cfg + +from cinder.brick import exception +from cinder.brick.iscsi import iscsi +from cinder.openstack.common.gettextutils import _ +from cinder.openstack.common import log as logging +from cinder.openstack.common import processutils as putils +from cinder import utils + +LOG = logging.getLogger(__name__) +CONF = cfg.CONF + + +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): + """Creates an export for a logical volume.""" + iscsi_name = "%s%s" % (CONF.iscsi_target_prefix, + volume['name']) + iscsi_target, lun = self._get_target_and_lun(context, volume) + 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) + data = {} + data['location'] = self._iscsi_location( + CONF.iscsi_ip_address, tid, iscsi_name, lun) + 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(_("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(_("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, + old_name=None): + iscsi_target = self._get_target_for_ensure_export(context, + volume['id']) + if iscsi_target is None: + LOG.info(_("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) + 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) + + def _ensure_iscsi_targets(self, context, host): + """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 >= CONF.iscsi_num_targets: + return + + # NOTE(vish): Target ids start at 1, not 0. + target_end = CONF.iscsi_num_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): + lun = 0 + self._ensure_iscsi_targets(context, volume['host']) + 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, lun=None): + return "%s:%s,%s %s %s" % (ip, CONF.iscsi_port, + target, iqn, lun) + + def _fix_id_migration(self, context, volume): + """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' % CONF.volume_group) + + try: + (out, err) = self._execute('readlink', old_name) + except putils.ProcessExecutionError: + link_path = '/dev/%s/%s' % (CONF.volume_group, + 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): + 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): + return { + 'location': "fake_location", + 'auth': "fake_auth" + } + + def remove_export(self, context, volume): + pass + + def ensure_export(self, context, volume_id, iscsi_name, volume_path, + 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(_("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_id, iscsi_name, volume_path, + old_name=None): + try: + volume_info = self.db.volume_get(context, volume_id) + (auth_method, + auth_user, + auth_pass) = volume_info['provider_auth'].split(' ', 3) + chap_auth = self._iscsi_authentication(auth_method, + auth_user, + auth_pass) + except exception.NotFound: + LOG.debug(_("volume_info:%s"), volume_info) + LOG.info(_("Skipping ensure_export. No iscsi_target " + "provision for volume: %s"), volume_id) + + 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): + pass