]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
VMEM v6000: Fix export verify routines
authorRyan Lucio <rlucio@vmem.com>
Thu, 12 Mar 2015 23:12:10 +0000 (16:12 -0700)
committerRyan Lucio <rlucio@vmem.com>
Thu, 9 Jul 2015 19:07:31 +0000 (12:07 -0700)
New 'state' nodes in vmos6 are reflecting the kernel/system state
indicating lun accessibility on the data plane.  The nodes used
previously were only reflecting success on the mgmt plane, meaning
that the routines were sometimes returning before the target/export
was actually available for client use.

Also, switch to using one global iscsi target group (backend
container that groups target IPs) to avoid a backend export bug
exposed by the changes mentioned above.

Change-Id: I1cd296d6d02562e1eb7374df736b2649fe2692a2
Closes-Bug: #1430014

cinder/tests/unit/test_v6000_common.py
cinder/tests/unit/test_v6000_fcp.py
cinder/tests/unit/test_v6000_iscsi.py
cinder/volume/drivers/violin/v6000_common.py
cinder/volume/drivers/violin/v6000_fcp.py
cinder/volume/drivers/violin/v6000_iscsi.py

index e622dd9be1ae9f700382eee4e0540d2613dc79c7..f37a1a93100e0b912713be523ec8ab45b82de5c8 100644 (file)
@@ -497,12 +497,11 @@ class V6000CommonTestCase(test.TestCase):
         self.assertEqual(CONNECTOR['host'],
                          self.driver._get_igroup(VOLUME, CONNECTOR))
 
-    def test_wait_for_export_config(self):
-        """Queries to cluster nodes verify export config."""
-        bn = "/vshare/config/export/container/myContainer/lun/%s" \
+    def test_wait_for_export_state(self):
+        """Queries to cluster nodes verify export state."""
+        bn = "/vshare/state/local/container/myContainer/lun/%s/usn_id" \
             % VOLUME['id']
-        response = {'/vshare/config/export/container/myContainer/lun/vol-01':
-                    VOLUME['id']}
+        response = {bn: '012345'}
 
         conf = {
             'basic.get_node_values.return_value': response,
@@ -510,15 +509,17 @@ class V6000CommonTestCase(test.TestCase):
         self.driver.mga = self.setup_mock_vshare(m_conf=conf)
         self.driver.mgb = self.setup_mock_vshare(m_conf=conf)
 
-        result = self.driver._wait_for_export_config(VOLUME['id'], state=True)
+        result = self.driver._wait_for_export_state(VOLUME['id'], state=True)
 
         self.driver.mga.basic.get_node_values.assert_called_with(bn)
         self.driver.mgb.basic.get_node_values.assert_called_with(bn)
         self.assertTrue(result)
 
-    def test_wait_for_export_config_with_no_config(self):
-        """Queries to cluster nodes verify *no* export config."""
-        response = {}
+    def test_wait_for_export_state_with_no_state(self):
+        """Queries to cluster nodes verify *no* export state."""
+        bn = "/vshare/state/local/container/myContainer/lun/%s/usn_id" \
+            % VOLUME['id']
+        response = {bn: '(not exported)'}
 
         conf = {
             'basic.get_node_values.return_value': response,
@@ -526,7 +527,7 @@ class V6000CommonTestCase(test.TestCase):
         self.driver.mga = self.setup_mock_vshare(m_conf=conf)
         self.driver.mgb = self.setup_mock_vshare(m_conf=conf)
 
-        self.assertTrue(self.driver._wait_for_export_config(
+        self.assertTrue(self.driver._wait_for_export_state(
             VOLUME['id'], state=False))
 
     def test_is_supported_vmos_version(self):
index 9b11320d90dc32dd2cbda79fae46c48e55952c79..280df3d933025fb46aa8f59b47f0d3f91d1a8859 100644 (file)
@@ -319,9 +319,9 @@ class V6000FCPDriverTestCase(test.TestCase):
 
         self.driver.common._send_cmd_and_verify.assert_called_with(
             self.driver.common.vip.lun.export_lun,
-            self.driver.common._wait_for_export_config, '',
+            self.driver.common._wait_for_export_state, '',
             [self.driver.common.container, VOLUME['id'], 'all',
-             igroup, 'auto'], [VOLUME['id'], 'state=True'])
+             igroup, 'auto'], [VOLUME['id'], None, True])
         self.driver.common._get_lun_id.assert_called_with(VOLUME['id'])
         self.assertEqual(lun_id, result)
 
@@ -350,9 +350,9 @@ class V6000FCPDriverTestCase(test.TestCase):
 
         self.driver.common._send_cmd_and_verify.assert_called_with(
             self.driver.common.vip.lun.unexport_lun,
-            self.driver.common._wait_for_export_config, '',
+            self.driver.common._wait_for_export_state, '',
             [self.driver.common.container, VOLUME['id'], 'all', 'all', 'auto'],
-            [VOLUME['id'], 'state=False'])
+            [VOLUME['id'], None, False])
         self.assertTrue(result is None)
 
     def test_unexport_lun_fails_with_exception(self):
@@ -372,7 +372,7 @@ class V6000FCPDriverTestCase(test.TestCase):
 
         self.driver.common.vip = self.setup_mock_vshare()
         self.driver.common._send_cmd = mock.Mock(return_value=response)
-        self.driver.common._wait_for_export_config = mock.Mock()
+        self.driver.common._wait_for_export_state = mock.Mock()
         self.driver.common._get_snapshot_id = mock.Mock(return_value=lun_id)
 
         result = self.driver._export_snapshot(SNAPSHOT, CONNECTOR, igroup)
@@ -381,7 +381,7 @@ class V6000FCPDriverTestCase(test.TestCase):
             self.driver.common.vip.snapshot.export_lun_snapshot, '',
             self.driver.common.container, SNAPSHOT['volume_id'],
             SNAPSHOT['id'], igroup, 'all', 'auto')
-        self.driver.common._wait_for_export_config.assert_called_with(
+        self.driver.common._wait_for_export_state.assert_called_with(
             SNAPSHOT['volume_id'], SNAPSHOT['id'], state=True)
         self.driver.common._get_snapshot_id.assert_called_once_with(
             SNAPSHOT['volume_id'], SNAPSHOT['id'])
@@ -392,7 +392,7 @@ class V6000FCPDriverTestCase(test.TestCase):
 
         self.driver.common.vip = self.setup_mock_vshare()
         self.driver.common._send_cmd = mock.Mock(return_value=response)
-        self.driver.common._wait_for_export_config = mock.Mock()
+        self.driver.common._wait_for_export_state = mock.Mock()
 
         result = self.driver._unexport_snapshot(SNAPSHOT)
 
@@ -400,7 +400,7 @@ class V6000FCPDriverTestCase(test.TestCase):
             self.driver.common.vip.snapshot.unexport_lun_snapshot, '',
             self.driver.common.container, SNAPSHOT['volume_id'],
             SNAPSHOT['id'], 'all', 'all', 'auto', False)
-        self.driver.common._wait_for_export_config.assert_called_with(
+        self.driver.common._wait_for_export_state.assert_called_with(
             SNAPSHOT['volume_id'], SNAPSHOT['id'], state=False)
         self.assertTrue(result is None)
 
index b884ba3ff9319b43eb40f9676eceae4fbc9c33b7..ae5841392171ff5b6c9e488caeaa42ef76f2f996 100644 (file)
@@ -240,9 +240,10 @@ class V6000ISCSIDriverTestCase(test.TestCase):
     def test_initialize_connection(self):
         lun_id = 1
         igroup = None
+        target_name = self.driver.TARGET_GROUP_NAME
         tgt = self.driver.array_info[0]
         iqn = "%s%s:%s" % (self.conf.iscsi_target_prefix,
-                           tgt['node'], VOLUME['id'])
+                           tgt['node'], target_name)
         volume = mock.MagicMock(spec=models.Volume)
 
         def getitem(name):
@@ -251,15 +252,14 @@ class V6000ISCSIDriverTestCase(test.TestCase):
         volume.__getitem__.side_effect = getitem
 
         self.driver.common.vip = self.setup_mock_vshare()
-        self.driver._get_short_name = mock.Mock(return_value=VOLUME['id'])
-        self.driver._create_iscsi_target = mock.Mock(return_value=tgt)
+        self.driver._get_iscsi_target = mock.Mock(return_value=tgt)
         self.driver._export_lun = mock.Mock(return_value=lun_id)
 
         props = self.driver.initialize_connection(volume, CONNECTOR)
 
-        self.driver._get_short_name.assert_called_with(volume['id'])
-        self.driver._create_iscsi_target.assert_called_with(volume)
-        self.driver._export_lun.assert_called_with(volume, CONNECTOR, igroup)
+        self.driver._get_iscsi_target.assert_called_once_with()
+        self.driver._export_lun.assert_called_once_with(
+            volume, CONNECTOR, igroup)
         self.driver.common.vip.basic.save_config.assert_called_with()
         self.assertEqual("1.2.3.4:3260", props['data']['target_portal'])
         self.assertEqual(iqn, props['data']['target_iqn'])
@@ -269,9 +269,10 @@ class V6000ISCSIDriverTestCase(test.TestCase):
     def test_initialize_connection_with_snapshot_object(self):
         lun_id = 1
         igroup = None
+        target_name = self.driver.TARGET_GROUP_NAME
         tgt = self.driver.array_info[0]
         iqn = "%s%s:%s" % (self.conf.iscsi_target_prefix,
-                           tgt['node'], SNAPSHOT['id'])
+                           tgt['node'], target_name)
         snapshot = mock.MagicMock(spec=models.Snapshot)
 
         def getitem(name):
@@ -280,16 +281,14 @@ class V6000ISCSIDriverTestCase(test.TestCase):
         snapshot.__getitem__.side_effect = getitem
 
         self.driver.common.vip = self.setup_mock_vshare()
-        self.driver._get_short_name = mock.Mock(return_value=SNAPSHOT['id'])
-        self.driver._create_iscsi_target = mock.Mock(return_value=tgt)
+        self.driver._get_iscsi_target = mock.Mock(return_value=tgt)
         self.driver._export_snapshot = mock.Mock(return_value=lun_id)
 
         props = self.driver.initialize_connection(snapshot, CONNECTOR)
 
-        self.driver._get_short_name.assert_called_with(SNAPSHOT['id'])
-        self.driver._create_iscsi_target.assert_called_with(snapshot)
-        self.driver._export_snapshot.assert_called_with(snapshot, CONNECTOR,
-                                                        igroup)
+        self.driver._get_iscsi_target.assert_called_once_with()
+        self.driver._export_snapshot.assert_called_once_with(
+            snapshot, CONNECTOR, igroup)
         self.driver.common.vip.basic.save_config.assert_called_with()
         self.assertEqual("1.2.3.4:3260", props['data']['target_portal'])
         self.assertEqual(iqn, props['data']['target_iqn'])
@@ -300,9 +299,10 @@ class V6000ISCSIDriverTestCase(test.TestCase):
         self.conf.use_igroups = True
         lun_id = 1
         igroup = 'test-igroup-1'
+        target_name = self.driver.TARGET_GROUP_NAME
         tgt = self.driver.array_info[0]
         iqn = "%s%s:%s" % (self.conf.iscsi_target_prefix,
-                           tgt['node'], VOLUME['id'])
+                           tgt['node'], target_name)
         volume = mock.MagicMock(spec=models.Volume)
 
         def getitem(name):
@@ -313,18 +313,19 @@ class V6000ISCSIDriverTestCase(test.TestCase):
         self.driver.common.vip = self.setup_mock_vshare()
         self.driver.common._get_igroup = mock.Mock(return_value=igroup)
         self.driver._add_igroup_member = mock.Mock()
-        self.driver._get_short_name = mock.Mock(return_value=VOLUME['id'])
-        self.driver._create_iscsi_target = mock.Mock(return_value=tgt)
+        self.driver._get_iscsi_target = mock.Mock(return_value=tgt)
         self.driver._export_lun = mock.Mock(return_value=lun_id)
 
         props = self.driver.initialize_connection(volume, CONNECTOR)
 
-        self.driver.common._get_igroup.assert_called_with(volume, CONNECTOR)
-        self.driver._add_igroup_member.assert_called_with(CONNECTOR, igroup)
-        self.driver._get_short_name.assert_called_with(volume['id'])
-        self.driver._create_iscsi_target.assert_called_with(volume)
-        self.driver._export_lun.assert_called_with(volume, CONNECTOR, igroup)
-        self.driver.common.vip.basic.save_config.assert_called_with()
+        self.driver.common._get_igroup.assert_called_once_with(
+            volume, CONNECTOR)
+        self.driver._add_igroup_member.assert_called_once_with(
+            CONNECTOR, igroup)
+        self.driver._get_iscsi_target.assert_called_once_with()
+        self.driver._export_lun.assert_called_once_with(
+            volume, CONNECTOR, igroup)
+        self.driver.common.vip.basic.save_config.assert_called_once_with()
         self.assertEqual("1.2.3.4:3260", props['data']['target_portal'])
         self.assertEqual(iqn, props['data']['target_iqn'])
         self.assertEqual(lun_id, props['data']['target_lun'])
@@ -335,12 +336,10 @@ class V6000ISCSIDriverTestCase(test.TestCase):
 
         self.driver.common.vip = self.setup_mock_vshare()
         self.driver._unexport_lun = mock.Mock()
-        self.driver._delete_iscsi_target = mock.Mock()
 
         result = self.driver.terminate_connection(volume, CONNECTOR)
 
-        self.driver._unexport_lun.assert_called_with(volume)
-        self.driver._delete_iscsi_target.assert_called_with(volume)
+        self.driver._unexport_lun.assert_called_once_with(volume)
         self.driver.common.vip.basic.save_config.assert_called_with()
         self.assertTrue(result is None)
 
@@ -349,12 +348,10 @@ class V6000ISCSIDriverTestCase(test.TestCase):
 
         self.driver.common.vip = self.setup_mock_vshare()
         self.driver._unexport_snapshot = mock.Mock()
-        self.driver._delete_iscsi_target = mock.Mock()
 
         result = self.driver.terminate_connection(snapshot, CONNECTOR)
 
-        self.driver._unexport_snapshot.assert_called_with(snapshot)
-        self.driver._delete_iscsi_target.assert_called_with(snapshot)
+        self.driver._unexport_snapshot.assert_called_once_with(snapshot)
         self.driver.common.vip.basic.save_config.assert_called_with()
         self.assertTrue(result is None)
 
@@ -367,82 +364,59 @@ class V6000ISCSIDriverTestCase(test.TestCase):
         self.driver._update_stats.assert_called_with()
         self.assertEqual(self.driver.stats, result)
 
-    def test_create_iscsi_target(self):
-        target_name = VOLUME['id']
-        response = {'code': 0, 'message': 'success'}
+    def test_create_iscsi_target_group(self):
+        target_name = self.driver.TARGET_GROUP_NAME
+        bn = "/vshare/config/iscsi/target/%s" % target_name
+        response1 = {}
+        response2 = {'code': 0, 'message': 'success'}
 
-        m_vshare = self.setup_mock_vshare()
+        conf = {
+            'basic.get_node_values.return_value': response1,
+        }
+        m_vshare = self.setup_mock_vshare(conf)
 
         self.driver.common.vip = m_vshare
         self.driver.common.mga = m_vshare
         self.driver.common.mgb = m_vshare
-        self.driver._get_short_name = mock.Mock(return_value=VOLUME['id'])
         self.driver.common._send_cmd_and_verify = mock.Mock(
-            return_value=response)
-        self.driver.common._send_cmd = mock.Mock(return_value=response)
+            return_value=response2)
+        self.driver.common._send_cmd = mock.Mock(return_value=response2)
 
         calls = [mock.call(self.driver.common.mga.iscsi.bind_ip_to_target, '',
-                           VOLUME['id'],
+                           target_name,
                            self.driver.gateway_iscsi_ip_addresses_mga),
                  mock.call(self.driver.common.mgb.iscsi.bind_ip_to_target, '',
-                           VOLUME['id'],
+                           target_name,
                            self.driver.gateway_iscsi_ip_addresses_mgb)]
 
-        result = self.driver._create_iscsi_target(VOLUME)
+        result = self.driver._create_iscsi_target_group()
 
-        self.driver._get_short_name.assert_called_with(VOLUME['id'])
+        self.driver.common.vip.basic.get_node_values.assert_called_with(bn)
         self.driver.common._send_cmd_and_verify.assert_called_with(
             self.driver.common.vip.iscsi.create_iscsi_target,
-            self.driver._wait_for_targetstate, '',
+            self.driver._wait_for_target_state, '',
             [target_name], [target_name])
         self.driver.common._send_cmd.assert_has_calls(calls)
-        self.assertTrue(result in self.driver.array_info)
-
-    def test_delete_iscsi_target(self):
-        response = {'code': 0, 'message': 'success'}
-
-        self.driver.common.vip = self.setup_mock_vshare()
-        self.driver._get_short_name = mock.Mock(return_value=VOLUME['id'])
-        self.driver.common._send_cmd = mock.Mock(return_value=response)
-
-        result = self.driver._delete_iscsi_target(VOLUME)
-
-        self.driver._get_short_name.assert_called_with(VOLUME['id'])
-        self.driver.common._send_cmd(
-            self.driver.common.vip.iscsi.delete_iscsi_target,
-            '', VOLUME['id'])
         self.assertTrue(result is None)
 
-    def test_delete_iscsi_target_fails_with_exception(self):
-        response = {'code': 14000, 'message': 'Generic error'}
-        failure = exception.ViolinBackendErr
-
-        self.driver.common.vip = self.setup_mock_vshare()
-        self.driver._get_short_name = mock.Mock(return_value=VOLUME['id'])
-        self.driver.common._send_cmd = mock.Mock(
-            side_effect=failure(response['message']))
-
-        self.assertRaises(failure, self.driver._delete_iscsi_target, VOLUME)
-
     def test_export_lun(self):
+        target_name = self.driver.TARGET_GROUP_NAME
         igroup = 'test-igroup-1'
         lun_id = '1'
         response = {'code': 0, 'message': ''}
 
         self.driver.common.vip = self.setup_mock_vshare()
-        self.driver._get_short_name = mock.Mock(return_value=VOLUME['id'])
         self.driver.common._send_cmd_and_verify = mock.Mock(
             return_value=response)
         self.driver.common._get_lun_id = mock.Mock(return_value=lun_id)
 
         result = self.driver._export_lun(VOLUME, CONNECTOR, igroup)
 
-        self.driver._get_short_name.assert_called_with(VOLUME['id'])
         self.driver.common._send_cmd_and_verify.assert_called_with(
             self.driver.common.vip.lun.export_lun,
-            self.driver.common._wait_for_export_config, '',
-            [self.driver.common.container, VOLUME['id'], VOLUME['id'],
-             igroup, 'auto'], [VOLUME['id'], 'state=True'])
+            self.driver.common._wait_for_export_state, '',
+            [self.driver.common.container, VOLUME['id'], target_name,
+             igroup, 'auto'], [VOLUME['id'], None, True])
         self.driver.common._get_lun_id.assert_called_with(VOLUME['id'])
         self.assertEqual(lun_id, result)
 
@@ -453,7 +427,6 @@ class V6000ISCSIDriverTestCase(test.TestCase):
         failure = exception.ViolinBackendErr
 
         self.driver.common.vip = self.setup_mock_vshare()
-        self.driver._get_short_name = mock.Mock(return_value=VOLUME['id'])
         self.driver.common._send_cmd_and_verify = mock.Mock(
             side_effect=failure(response['message']))
         self.driver._get_lun_id = mock.Mock(return_value=lun_id)
@@ -472,9 +445,9 @@ class V6000ISCSIDriverTestCase(test.TestCase):
 
         self.driver.common._send_cmd_and_verify.assert_called_with(
             self.driver.common.vip.lun.unexport_lun,
-            self.driver.common._wait_for_export_config, '',
+            self.driver.common._wait_for_export_state, '',
             [self.driver.common.container, VOLUME['id'], 'all', 'all', 'auto'],
-            [VOLUME['id'], 'state=False'])
+            [VOLUME['id'], None, False])
         self.assertTrue(result is None)
 
     def test_unexport_lun_fails_with_exception(self):
@@ -489,23 +462,22 @@ class V6000ISCSIDriverTestCase(test.TestCase):
 
     def test_export_snapshot(self):
         lun_id = '1'
+        target_name = self.driver.TARGET_GROUP_NAME
         igroup = 'test-igroup-1'
         response = {'code': 0, 'message': ''}
 
         self.driver.common.vip = self.setup_mock_vshare()
-        self.driver._get_short_name = mock.Mock(return_value=SNAPSHOT['id'])
         self.driver.common._send_cmd = mock.Mock(return_value=response)
-        self.driver.common._wait_for_export_config = mock.Mock()
+        self.driver.common._wait_for_export_state = mock.Mock()
         self.driver.common._get_snapshot_id = mock.Mock(return_value=lun_id)
 
         result = self.driver._export_snapshot(SNAPSHOT, CONNECTOR, igroup)
 
-        self.driver._get_short_name.assert_called_with(SNAPSHOT['id'])
         self.driver.common._send_cmd.assert_called_with(
             self.driver.common.vip.snapshot.export_lun_snapshot, '',
             self.driver.common.container, SNAPSHOT['volume_id'],
-            SNAPSHOT['id'], igroup, SNAPSHOT['id'], 'auto')
-        self.driver.common._wait_for_export_config.assert_called_with(
+            SNAPSHOT['id'], igroup, target_name, 'auto')
+        self.driver.common._wait_for_export_state.assert_called_with(
             SNAPSHOT['volume_id'], SNAPSHOT['id'], state=True)
         self.driver.common._get_snapshot_id.assert_called_once_with(
             SNAPSHOT['volume_id'], SNAPSHOT['id'])
@@ -517,7 +489,7 @@ class V6000ISCSIDriverTestCase(test.TestCase):
 
         self.driver.common.vip = self.setup_mock_vshare()
         self.driver.common._send_cmd = mock.Mock(return_value=response)
-        self.driver.common._wait_for_export_config = mock.Mock()
+        self.driver.common._wait_for_export_state = mock.Mock()
 
         result = self.driver._unexport_snapshot(SNAPSHOT)
 
@@ -525,7 +497,7 @@ class V6000ISCSIDriverTestCase(test.TestCase):
             self.driver.common.vip.snapshot.unexport_lun_snapshot, '',
             self.driver.common.container, SNAPSHOT['volume_id'],
             SNAPSHOT['id'], 'all', 'all', 'auto', False)
-        self.driver.common._wait_for_export_config.assert_called_with(
+        self.driver.common._wait_for_export_state.assert_called_with(
             SNAPSHOT['volume_id'], SNAPSHOT['id'], state=False)
         self.assertTrue(result is None)
 
@@ -723,9 +695,9 @@ class V6000ISCSIDriverTestCase(test.TestCase):
 
         self.assertEqual(self.conf.san_ip, self.driver._get_hostname())
 
-    def test_wait_for_targetstate(self):
+    def test_wait_for_target_state(self):
         target = 'mytarget'
-        bn = "/vshare/config/iscsi/target/%s" % target
+        bn = "/vshare/state/local/target/iscsi/%s" % target
         response = {bn: target}
 
         conf = {
@@ -734,7 +706,7 @@ class V6000ISCSIDriverTestCase(test.TestCase):
         self.driver.common.mga = self.setup_mock_vshare(m_conf=conf)
         self.driver.common.mgb = self.setup_mock_vshare(m_conf=conf)
 
-        result = self.driver._wait_for_targetstate(target)
+        result = self.driver._wait_for_target_state(target)
 
         self.driver.common.mga.basic.get_node_values.assert_called_with(bn)
         self.driver.common.mgb.basic.get_node_values.assert_called_with(bn)
index 6d877fd459cf06367655c6decbc2c68752657d20..a777d7d4f66062cfce590cb769219fb7bd816566 100644 (file)
@@ -48,7 +48,6 @@ if vmemclient:
     LOG.info(_LI("Running with vmemclient version: %s."),
              vmemclient.__version__)
 
-# version vmos versions V6.3.0.4 or newer
 VMOS_SUPPORTED_VERSION_PATTERNS = ['V6.3.0.[4-9]', 'V6.3.[1-9].?[0-9]?']
 
 violin_opts = [
@@ -75,9 +74,10 @@ class V6000Common(object):
 
     Version history:
         1.0 - Initial driver
+        1.0.1 - Fixes polling for export completion
     """
 
-    VERSION = '1.0'
+    VERSION = '1.0.1'
 
     def __init__(self, config):
         self.vip = None
@@ -500,9 +500,9 @@ class V6000Common(object):
 
         return igroup_name
 
-    def _wait_for_export_config(self, volume_name, snapshot_name=None,
-                                state=False):
-        """Polls backend to verify volume's export configuration.
+    def _wait_for_export_state(self, volume_name, snapshot_name=None,
+                               state=False):
+        """Polls backend to verify volume's export state.
 
         XG sets/queries following a request to create or delete a lun
         export may fail on the backend if vshared is still processing
@@ -524,27 +524,41 @@ class V6000Common(object):
             (depending on 'state' param)
         """
         if not snapshot_name:
-            bn = "/vshare/config/export/container/%s/lun/%s" \
+            bn = "/vshare/state/local/container/%s/lun/%s/usn_id" \
                 % (self.container, volume_name)
         else:
-            bn = "/vshare/config/export/snapshot/container/%s/lun/%s/snap/%s" \
+            bn = "/vshare/state/snapshot/container/%s/lun/%s/snap/%s/usn_id" \
                 % (self.container, volume_name, snapshot_name)
 
         def _loop_func(state):
             status = [False, False]
             mg_conns = [self.mga, self.mgb]
 
-            LOG.debug("Entering _wait_for_export_config loop: state=%s.",
+            LOG.debug("Entering _wait_for_export_state loop: state=%s.",
                       state)
 
+            # TODO(rlucio): May need to handle situations where export
+            # fails, i.e., HBAs go offline and the array is in
+            # degraded mode.
+            #
             for node_id in range(2):
                 resp = mg_conns[node_id].basic.get_node_values(bn)
-                if state and len(resp.keys()):
-                    status[node_id] = True
-                elif (not state) and (not len(resp.keys())):
-                    status[node_id] = True
+
+                if state:
+                    # Verify export was added.  Validates when the usn_id is
+                    # altered to a non-default binding string.
+                    #
+                    if resp[bn] != "(not exported)":
+                        status[node_id] = True
+                else:
+                    # Verify export was removed.  Validates when the usn_id is
+                    # reset to the default binding string.
+                    #
+                    if resp[bn] == "(not exported)":
+                        status[node_id] = True
 
             if status[0] and status[1]:
+                LOG.debug("_wait_for_export_state loopingcall complete.")
                 raise loopingcall.LoopingCallDone(retvalue=True)
 
         timer = loopingcall.FixedIntervalLoopingCall(_loop_func, state)
index 1d9ecb1c0b0f1cb4304c8a670747586f7760b76d..b9a3b10ca994930be7946d004a0c71119db0c114 100644 (file)
@@ -58,9 +58,10 @@ class V6000FCDriver(driver.FibreChannelDriver):
 
     Version history:
         1.0 - Initial driver
+        1.0.1 - Fixes polling for export completion
     """
 
-    VERSION = '1.0'
+    VERSION = '1.0.1'
 
     def __init__(self, *args, **kwargs):
         super(V6000FCDriver, self).__init__(*args, **kwargs)
@@ -236,9 +237,9 @@ class V6000FCDriver(driver.FibreChannelDriver):
 
         try:
             self.common._send_cmd_and_verify(
-                v.lun.export_lun, self.common._wait_for_export_config, '',
+                v.lun.export_lun, self.common._wait_for_export_state, '',
                 [self.common.container, volume['id'], 'all', export_to,
-                 'auto'], [volume['id'], 'state=True'])
+                 'auto'], [volume['id'], None, True])
 
         except Exception:
             LOG.exception(_LE("LUN export for %s failed!"), volume['id'])
@@ -264,9 +265,9 @@ class V6000FCDriver(driver.FibreChannelDriver):
 
         try:
             self.common._send_cmd_and_verify(
-                v.lun.unexport_lun, self.common._wait_for_export_config, '',
+                v.lun.unexport_lun, self.common._wait_for_export_state, '',
                 [self.common.container, volume['id'], 'all', 'all', 'auto'],
-                [volume['id'], 'state=False'])
+                [volume['id'], None, False])
 
         except exception.ViolinBackendErrNotFound:
             LOG.debug("Lun %s already unexported, continuing.", volume['id'])
@@ -315,8 +316,8 @@ class V6000FCDriver(driver.FibreChannelDriver):
             raise
 
         else:
-            self.common._wait_for_export_config(snapshot['volume_id'],
-                                                snapshot['id'], state=True)
+            self.common._wait_for_export_state(snapshot['volume_id'],
+                                               snapshot['id'], state=True)
             lun_id = self.common._get_snapshot_id(snapshot['volume_id'],
                                                   snapshot['id'])
 
@@ -347,8 +348,8 @@ class V6000FCDriver(driver.FibreChannelDriver):
             raise
 
         else:
-            self.common._wait_for_export_config(snapshot['volume_id'],
-                                                snapshot['id'], state=False)
+            self.common._wait_for_export_state(snapshot['volume_id'],
+                                               snapshot['id'], state=False)
 
     def _add_igroup_member(self, connector, igroup):
         """Add an initiator to the openstack igroup so it can see exports.
index 509e6e536ebc51e6ff913fbbd506b5af8f519522..a6534b98daf1ce876607440e8d5b7af0f88864a6 100644 (file)
@@ -57,9 +57,11 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
 
     Version history:
         1.0 - Initial driver
+        1.0.1 - Fixes polling for export completion
     """
 
-    VERSION = '1.0'
+    VERSION = '1.0.1'
+    TARGET_GROUP_NAME = 'openstack'
 
     def __init__(self, *args, **kwargs):
         super(V6000ISCSIDriver, self).__init__(*args, **kwargs)
@@ -92,6 +94,9 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
                                     "addr": ip,
                                     "conn": self.common.mgb})
 
+        # setup global target group for exports to use
+        self._create_iscsi_target_group()
+
     def check_for_setup_error(self):
         """Returns an error if prerequisites aren't met."""
         self.common.check_for_setup_error()
@@ -175,15 +180,16 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
             igroup = self.common._get_igroup(volume, connector)
             self._add_igroup_member(connector, igroup)
 
-        vol = self._get_short_name(volume['id'])
-        tgt = self._create_iscsi_target(volume)
+        tgt = self._get_iscsi_target()
+        target_name = self.TARGET_GROUP_NAME
+
         if isinstance(volume, models.Volume):
             lun = self._export_lun(volume, connector, igroup)
         else:
             lun = self._export_snapshot(volume, connector, igroup)
 
         iqn = "%s%s:%s" % (self.configuration.iscsi_target_prefix,
-                           tgt['node'], vol)
+                           tgt['node'], target_name)
         self.common.vip.basic.save_config()
 
         properties = {}
@@ -205,7 +211,6 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
             self._unexport_lun(volume)
         else:
             self._unexport_snapshot(volume)
-        self._delete_iscsi_target(volume)
         self.common.vip.basic.save_config()
 
     def get_volume_stats(self, refresh=False):
@@ -214,32 +219,31 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
             self._update_stats()
         return self.stats
 
-    @utils.synchronized('vmem-export')
-    def _create_iscsi_target(self, volume):
+    def _create_iscsi_target_group(self):
         """Creates a new target for use in exporting a lun.
 
-        Openstack does not yet support multipathing. We still create
-        HA targets but we pick a single random target for the
-        Openstack infrastructure to use.  This at least allows us to
-        evenly distribute LUN connections across the storage cluster.
+        Create an HA target on the backend that will be used for all
+        lun exports made via this driver.
+
         The equivalent CLI commands are "iscsi target create
         <target_name>" and "iscsi target bind <target_name> to
         <ip_of_mg_eth_intf>".
-
-        Arguments:
-            volume -- volume object provided by the Manager
-
-        Returns:
-            reference to randomly selected target object
         """
         v = self.common.vip
-        target_name = self._get_short_name(volume['id'])
+        target_name = self.TARGET_GROUP_NAME
+
+        bn = "/vshare/config/iscsi/target/%s" % target_name
+        resp = self.common.vip.basic.get_node_values(bn)
+
+        if resp:
+            LOG.debug("iscsi target group %s already exists.", target_name)
+            return
 
         LOG.debug("Creating iscsi target %s.", target_name)
 
         try:
             self.common._send_cmd_and_verify(v.iscsi.create_iscsi_target,
-                                             self._wait_for_targetstate,
+                                             self._wait_for_target_state,
                                              '', [target_name], [target_name])
 
         except Exception:
@@ -253,33 +257,20 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
             self.common._send_cmd(self.common.mgb.iscsi.bind_ip_to_target,
                                   '', target_name,
                                   self.gateway_iscsi_ip_addresses_mgb)
+
         except Exception:
             LOG.exception(_LE("Failed to bind iSCSI targets!"))
             raise
 
-        return self.array_info[random.randint(0, len(self.array_info) - 1)]
-
-    @utils.synchronized('vmem-export')
-    def _delete_iscsi_target(self, volume):
-        """Deletes the iscsi target for a lun.
-
-        The CLI equivalent is "no iscsi target create <target_name>".
+    def _get_iscsi_target(self):
+        """Get a random target IP for OpenStack to connect to.
 
-        Arguments:
-            volume -- volume object provided by the Manager
+        For the non-multipath case we pick a single random target for
+        the Openstack infrastructure to use.  This at least allows us
+        to evenly distribute LUN connections across the storage
+        cluster.
         """
-        v = self.common.vip
-        success_msgs = ['', 'Invalid target']
-        target_name = self._get_short_name(volume['id'])
-
-        LOG.debug("Deleting iscsi target for %s.", target_name)
-
-        try:
-            self.common._send_cmd(v.iscsi.delete_iscsi_target,
-                                  success_msgs, target_name)
-        except Exception:
-            LOG.exception(_LE("Failed to delete iSCSI target!"))
-            raise
+        return self.array_info[random.randint(0, len(self.array_info) - 1)]
 
     @utils.synchronized('vmem-export')
     def _export_lun(self, volume, connector=None, igroup=None):
@@ -307,15 +298,15 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
         else:
             raise exception.Error(_("No initiators found, cannot proceed"))
 
-        target_name = self._get_short_name(volume['id'])
+        target_name = self.TARGET_GROUP_NAME
 
         LOG.debug("Exporting lun %s.", volume['id'])
 
         try:
             self.common._send_cmd_and_verify(
-                v.lun.export_lun, self.common._wait_for_export_config, '',
+                v.lun.export_lun, self.common._wait_for_export_state, '',
                 [self.common.container, volume['id'], target_name,
-                 export_to, 'auto'], [volume['id'], 'state=True'])
+                 export_to, 'auto'], [volume['id'], None, True])
 
         except Exception:
             LOG.exception(_LE("LUN export for %s failed!"), volume['id'])
@@ -341,9 +332,9 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
 
         try:
             self.common._send_cmd_and_verify(
-                v.lun.unexport_lun, self.common._wait_for_export_config, '',
+                v.lun.unexport_lun, self.common._wait_for_export_state, '',
                 [self.common.container, volume['id'], 'all', 'all', 'auto'],
-                [volume['id'], 'state=False'])
+                [volume['id'], None, False])
 
         except exception.ViolinBackendErrNotFound:
             LOG.debug("Lun %s already unexported, continuing.", volume['id'])
@@ -371,7 +362,7 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
         export_to = ''
         v = self.common.vip
 
-        target_name = self._get_short_name(snapshot['id'])
+        target_name = self.TARGET_GROUP_NAME
 
         LOG.debug("Exporting snapshot %s.", snapshot['id'])
 
@@ -394,8 +385,8 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
             raise
 
         else:
-            self.common._wait_for_export_config(snapshot['volume_id'],
-                                                snapshot['id'], state=True)
+            self.common._wait_for_export_state(snapshot['volume_id'],
+                                               snapshot['id'], state=True)
             lun_id = self.common._get_snapshot_id(snapshot['volume_id'],
                                                   snapshot['id'])
 
@@ -426,8 +417,8 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
             raise
 
         else:
-            self.common._wait_for_export_config(snapshot['volume_id'],
-                                                snapshot['id'], state=False)
+            self.common._wait_for_export_state(snapshot['volume_id'],
+                                               snapshot['id'], state=False)
 
     def _add_igroup_member(self, connector, igroup):
         """Add an initiator to an igroup so it can see exports.
@@ -567,7 +558,7 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
 
         return hostname
 
-    def _wait_for_targetstate(self, target_name):
+    def _wait_for_target_state(self, target_name):
         """Polls backend to verify an iscsi target configuration.
 
         This function will try to verify the creation of an iscsi
@@ -577,15 +568,15 @@ class V6000ISCSIDriver(driver.ISCSIDriver):
             target_name -- name of iscsi target to be polled
 
         Returns:
-            True if the export state was correctly added
+            True if the target state was correctly added
         """
-        bn = "/vshare/config/iscsi/target/%s" % (target_name)
+        bn = "/vshare/state/local/target/iscsi/%s" % (target_name)
 
         def _loop_func():
             status = [False, False]
             mg_conns = [self.common.mga, self.common.mgb]
 
-            LOG.debug("Entering _wait_for_targetstate loop: target=%s.",
+            LOG.debug("Entering _wait_for_target_state loop: target=%s.",
                       target_name)
 
             for node_id in range(2):