VMEM v6000: Fix export verify routines

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
This commit is contained in:
Ryan Lucio 2015-03-12 16:12:10 -07:00
parent 917db3a296
commit ad5eb92dcd
6 changed files with 156 additions and 177 deletions

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

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

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

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

View 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.

View 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)]
def _get_iscsi_target(self):
"""Get a random target IP for OpenStack to connect to.
@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>".
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):