]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Move create_, remove_ and ensure_export from drivers
authorAnn Kamyshnikova <akamyshnikova@mirantis.com>
Tue, 27 Aug 2013 12:17:25 +0000 (16:17 +0400)
committerAnn Kamyshnikova <akamyshnikova@mirantis.com>
Tue, 11 Feb 2014 10:43:48 +0000 (14:43 +0400)
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

cinder/tests/test_block_device.py
cinder/tests/test_iscsi.py
cinder/volume/driver.py
cinder/volume/drivers/block_device.py
cinder/volume/drivers/lvm.py
cinder/volume/iscsi.py [new file with mode: 0644]

index c96156dcc38d987c0d4184cf675fa39428af8653..7def672fdc6fe856c1bb6e26993cdef106d3e79a 100644 (file)
@@ -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}
index 99e15887dd322a221bb8028dd9af9614f5107c36..87bb1c6e096c798ca45ba8d1b637475bcf551d88 100644 (file)
@@ -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)
index 544f81bf6ca9bdd9135917254372c466c31cfd71..cd96acc1e702ecfe70c82cdfc132993d5afd58da 100644 (file)
@@ -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):
index 2150cd326ae75dbe7c0f3f0f256dabd591534c90..e2ce8ef2da0f6f7fc6c4c900f647837039531049 100644 (file)
@@ -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):
index b5054f9eaa8946a1e47a394a30cfb2f8e4dd4bde..fe793288854c614d27b66ab6643e55a99a532aee 100644 (file)
@@ -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 (file)
index 0000000..807b1c1
--- /dev/null
@@ -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