Remove locks from LeftHand driver
By removing synchronization locks from the LeftHand driver, support for many simultaneous volume create/deletes and volume attach/detachs will be added. This is useful for high availability environments where performance with locks in place would be reduced. Removing the locks requires the creation of a new LeftHand client when a request is sent to the driver now. This allows requests to no longer be blocked if a request is already in process by the driver. This results in a performance increase for the driver and better stability in a high availability environment. Closes-Bug: #1395953 Change-Id: I39732d944a79bbfc597077fe0873d4cfcaace211
This commit is contained in:
parent
29f9fa9f2a
commit
331c40ce34
|
@ -22,7 +22,7 @@ import mock
|
|||
from cinder.tests import fake_hp_client_exceptions as hpexceptions
|
||||
|
||||
hplefthand = mock.Mock()
|
||||
hplefthand.version = "3.0.0"
|
||||
hplefthand.version = "1.0.3"
|
||||
hplefthand.exceptions = hpexceptions
|
||||
|
||||
sys.modules['hplefthandclient'] = hplefthand
|
||||
|
|
|
@ -679,6 +679,17 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
self.driver.do_setup(None)
|
||||
return _mock_client.return_value
|
||||
|
||||
@mock.patch('hplefthandclient.version', "1.0.0")
|
||||
def test_unsupported_client_version(self):
|
||||
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.setup_driver)
|
||||
|
||||
@mock.patch('hplefthandclient.version', "3.0.0")
|
||||
def test_supported_client_version(self):
|
||||
|
||||
self.setup_driver()
|
||||
|
||||
def test_create_volume(self):
|
||||
|
||||
# setup drive with default configuration
|
||||
|
@ -689,26 +700,33 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
mock_client.createVolume.return_value = {
|
||||
'iscsiIqn': self.connector['initiator']}
|
||||
|
||||
# execute driver
|
||||
volume_info = self.driver.create_volume(self.volume)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
self.assertEqual('10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0',
|
||||
volume_info['provider_location'])
|
||||
# execute driver
|
||||
volume_info = self.driver.create_volume(self.volume)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.createVolume(
|
||||
'fakevolume',
|
||||
1,
|
||||
units.Gi,
|
||||
{'isThinProvisioned': True, 'clusterName': 'CloudCluster1'})]
|
||||
self.assertEqual('10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0',
|
||||
volume_info['provider_location'])
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.createVolume(
|
||||
'fakevolume',
|
||||
1,
|
||||
units.Gi,
|
||||
{'isThinProvisioned': True,
|
||||
'clusterName': 'CloudCluster1'}),
|
||||
mock.call.logout()]
|
||||
|
||||
# mock HTTPServerError
|
||||
mock_client.createVolume.side_effect = hpexceptions.HTTPServerError()
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.create_volume, self.volume)
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
# mock HTTPServerError
|
||||
mock_client.createVolume.side_effect =\
|
||||
hpexceptions.HTTPServerError()
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.create_volume, self.volume)
|
||||
|
||||
@mock.patch.object(
|
||||
volume_types,
|
||||
|
@ -727,20 +745,26 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
mock_client.createVolume.return_value = {
|
||||
'iscsiIqn': self.connector['initiator']}
|
||||
|
||||
# execute creat_volume
|
||||
volume_info = self.driver.create_volume(volume_with_vt)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
self.assertEqual('10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0',
|
||||
volume_info['provider_location'])
|
||||
# execute create_volume
|
||||
volume_info = self.driver.create_volume(volume_with_vt)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.createVolume(
|
||||
'fakevolume',
|
||||
1,
|
||||
units.Gi,
|
||||
{'isThinProvisioned': False, 'clusterName': 'CloudCluster1'})]
|
||||
self.assertEqual('10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0',
|
||||
volume_info['provider_location'])
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.createVolume(
|
||||
'fakevolume',
|
||||
1,
|
||||
units.Gi,
|
||||
{'isThinProvisioned': False,
|
||||
'clusterName': 'CloudCluster1'}),
|
||||
mock.call.logout()]
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
def test_delete_volume(self):
|
||||
|
||||
|
@ -751,25 +775,31 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
# mock return value of getVolumeByName
|
||||
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
||||
|
||||
# execute delete_volume
|
||||
self.driver.delete_volume(self.volume)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.deleteVolume(self.volume_id)]
|
||||
# execute delete_volume
|
||||
self.driver.delete_volume(self.volume)
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.deleteVolume(self.volume_id),
|
||||
mock.call.logout()]
|
||||
|
||||
# mock HTTPNotFound (volume not found)
|
||||
mock_client.getVolumeByName.side_effect = hpexceptions.HTTPNotFound()
|
||||
# no exception should escape method
|
||||
self.driver.delete_volume(self.volume)
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
# mock HTTPConflict
|
||||
mock_client.deleteVolume.side_effect = hpexceptions.HTTPConflict()
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.delete_volume, self.volume_id)
|
||||
# mock HTTPNotFound (volume not found)
|
||||
mock_client.getVolumeByName.side_effect =\
|
||||
hpexceptions.HTTPNotFound()
|
||||
# no exception should escape method
|
||||
self.driver.delete_volume(self.volume)
|
||||
|
||||
# mock HTTPConflict
|
||||
mock_client.deleteVolume.side_effect = hpexceptions.HTTPConflict()
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.delete_volume, self.volume_id)
|
||||
|
||||
def test_extend_volume(self):
|
||||
|
||||
|
@ -780,21 +810,27 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
# mock return value of getVolumeByName
|
||||
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
||||
|
||||
# execute extend_volume
|
||||
self.driver.extend_volume(self.volume, 2)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.modifyVolume(1, {'size': 2 * units.Gi})]
|
||||
# execute extend_volume
|
||||
self.driver.extend_volume(self.volume, 2)
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.modifyVolume(1, {'size': 2 * units.Gi}),
|
||||
mock.call.logout()]
|
||||
|
||||
# mock HTTPServerError (array failure)
|
||||
mock_client.modifyVolume.side_effect = hpexceptions.HTTPServerError()
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.extend_volume, self.volume, 2)
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
# mock HTTPServerError (array failure)
|
||||
mock_client.modifyVolume.side_effect =\
|
||||
hpexceptions.HTTPServerError()
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.extend_volume, self.volume, 2)
|
||||
|
||||
def test_initialize_connection(self):
|
||||
|
||||
|
@ -810,37 +846,43 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
'iscsiSessions': None
|
||||
}
|
||||
|
||||
# execute initialize_connection
|
||||
result = self.driver.initialize_connection(
|
||||
self.volume,
|
||||
self.connector)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
# validate
|
||||
self.assertEqual(result['driver_volume_type'], 'iscsi')
|
||||
self.assertEqual(result['data']['target_discovered'], False)
|
||||
self.assertEqual(result['data']['volume_id'], self.volume_id)
|
||||
self.assertTrue('auth_method' not in result['data'])
|
||||
# execute initialize_connection
|
||||
result = self.driver.initialize_connection(
|
||||
self.volume,
|
||||
self.connector)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getServerByName('fakehost'),
|
||||
mock.call.createServer
|
||||
(
|
||||
'fakehost',
|
||||
'iqn.1993-08.org.debian:01:222',
|
||||
None
|
||||
),
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.addServerAccess(1, 0)]
|
||||
# validate
|
||||
self.assertEqual('iscsi', result['driver_volume_type'])
|
||||
self.assertEqual(False, result['data']['target_discovered'])
|
||||
self.assertEqual(self.volume_id, result['data']['volume_id'])
|
||||
self.assertTrue('auth_method' not in result['data'])
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getServerByName('fakehost'),
|
||||
mock.call.createServer
|
||||
(
|
||||
'fakehost',
|
||||
'iqn.1993-08.org.debian:01:222',
|
||||
None
|
||||
),
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.addServerAccess(1, 0),
|
||||
mock.call.logout()]
|
||||
|
||||
# mock HTTPServerError (array failure)
|
||||
mock_client.createServer.side_effect = hpexceptions.HTTPServerError()
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.driver.initialize_connection, self.volume, self.connector)
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
# mock HTTPServerError (array failure)
|
||||
mock_client.createServer.side_effect =\
|
||||
hpexceptions.HTTPServerError()
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.driver.initialize_connection, self.volume, self.connector)
|
||||
|
||||
def test_initialize_connection_session_exists(self):
|
||||
|
||||
|
@ -856,29 +898,34 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
'iscsiSessions': [{'server': {'uri': self.server_uri}}]
|
||||
}
|
||||
|
||||
# execute initialize_connection
|
||||
result = self.driver.initialize_connection(
|
||||
self.volume,
|
||||
self.connector)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
# validate
|
||||
self.assertEqual(result['driver_volume_type'], 'iscsi')
|
||||
self.assertEqual(result['data']['target_discovered'], False)
|
||||
self.assertEqual(result['data']['volume_id'], self.volume_id)
|
||||
self.assertTrue('auth_method' not in result['data'])
|
||||
# execute initialize_connection
|
||||
result = self.driver.initialize_connection(
|
||||
self.volume,
|
||||
self.connector)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getServerByName('fakehost'),
|
||||
mock.call.createServer
|
||||
(
|
||||
'fakehost',
|
||||
'iqn.1993-08.org.debian:01:222',
|
||||
None
|
||||
),
|
||||
mock.call.getVolumeByName('fakevolume')]
|
||||
# validate
|
||||
self.assertEqual('iscsi', result['driver_volume_type'])
|
||||
self.assertEqual(False, result['data']['target_discovered'])
|
||||
self.assertEqual(self.volume_id, result['data']['volume_id'])
|
||||
self.assertTrue('auth_method' not in result['data'])
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getServerByName('fakehost'),
|
||||
mock.call.createServer
|
||||
(
|
||||
'fakehost',
|
||||
'iqn.1993-08.org.debian:01:222',
|
||||
None
|
||||
),
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.logout()]
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
def test_initialize_connection_with_chaps(self):
|
||||
|
||||
|
@ -897,30 +944,35 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
'iscsiSessions': None
|
||||
}
|
||||
|
||||
# execute initialize_connection
|
||||
result = self.driver.initialize_connection(
|
||||
self.volume,
|
||||
self.connector)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
# validate
|
||||
self.assertEqual(result['driver_volume_type'], 'iscsi')
|
||||
self.assertEqual(result['data']['target_discovered'], False)
|
||||
self.assertEqual(result['data']['volume_id'], self.volume_id)
|
||||
self.assertEqual(result['data']['auth_method'], 'CHAP')
|
||||
# execute initialize_connection
|
||||
result = self.driver.initialize_connection(
|
||||
self.volume,
|
||||
self.connector)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getServerByName('fakehost'),
|
||||
mock.call.createServer
|
||||
(
|
||||
'fakehost',
|
||||
'iqn.1993-08.org.debian:01:222',
|
||||
None
|
||||
),
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.addServerAccess(1, 0)]
|
||||
# validate
|
||||
self.assertEqual('iscsi', result['driver_volume_type'])
|
||||
self.assertEqual(False, result['data']['target_discovered'])
|
||||
self.assertEqual(self.volume_id, result['data']['volume_id'])
|
||||
self.assertEqual('CHAP', result['data']['auth_method'])
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getServerByName('fakehost'),
|
||||
mock.call.createServer
|
||||
(
|
||||
'fakehost',
|
||||
'iqn.1993-08.org.debian:01:222',
|
||||
None
|
||||
),
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.addServerAccess(1, 0),
|
||||
mock.call.logout()]
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
def test_terminate_connection(self):
|
||||
|
||||
|
@ -931,24 +983,30 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
||||
mock_client.getServerByName.return_value = {'id': self.server_id}
|
||||
|
||||
# execute terminate_connection
|
||||
self.driver.terminate_connection(self.volume, self.connector)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.getServerByName('fakehost'),
|
||||
mock.call.removeServerAccess(1, 0)]
|
||||
# execute terminate_connection
|
||||
self.driver.terminate_connection(self.volume, self.connector)
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.getServerByName('fakehost'),
|
||||
mock.call.removeServerAccess(1, 0),
|
||||
mock.call.logout()]
|
||||
|
||||
mock_client.getVolumeByName.side_effect = hpexceptions.HTTPNotFound()
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.driver.terminate_connection,
|
||||
self.volume,
|
||||
self.connector)
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
mock_client.getVolumeByName.side_effect =\
|
||||
hpexceptions.HTTPNotFound()
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.driver.terminate_connection,
|
||||
self.volume,
|
||||
self.connector)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
|
||||
|
@ -958,25 +1016,31 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
|
||||
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
||||
|
||||
# execute create_snapshot
|
||||
self.driver.create_snapshot(self.snapshot)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.createSnapshot(
|
||||
'fakeshapshot',
|
||||
1,
|
||||
{'inheritAccess': True})]
|
||||
# execute create_snapshot
|
||||
self.driver.create_snapshot(self.snapshot)
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.createSnapshot(
|
||||
'fakeshapshot',
|
||||
1,
|
||||
{'inheritAccess': True}),
|
||||
mock.call.logout()]
|
||||
|
||||
# mock HTTPServerError (array failure)
|
||||
mock_client.getVolumeByName.side_effect = hpexceptions.HTTPNotFound()
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.driver.create_snapshot, self.snapshot)
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
# mock HTTPServerError (array failure)
|
||||
mock_client.getVolumeByName.side_effect =\
|
||||
hpexceptions.HTTPNotFound()
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.driver.create_snapshot, self.snapshot)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
|
||||
|
@ -986,39 +1050,46 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
|
||||
mock_client.getSnapshotByName.return_value = {'id': self.snapshot_id}
|
||||
|
||||
# execute delete_snapshot
|
||||
self.driver.delete_snapshot(self.snapshot)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getSnapshotByName('fakeshapshot'),
|
||||
mock.call.deleteSnapshot(3)]
|
||||
# execute delete_snapshot
|
||||
self.driver.delete_snapshot(self.snapshot)
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getSnapshotByName('fakeshapshot'),
|
||||
mock.call.deleteSnapshot(3),
|
||||
mock.call.logout()]
|
||||
|
||||
mock_client.getSnapshotByName.side_effect = hpexceptions.HTTPNotFound()
|
||||
# no exception is thrown, just error msg is logged
|
||||
self.driver.delete_snapshot(self.snapshot)
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
# mock HTTPServerError (array failure)
|
||||
ex = hpexceptions.HTTPServerError({'message': 'Some message.'})
|
||||
mock_client.getSnapshotByName.side_effect = ex
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.driver.delete_snapshot,
|
||||
self.snapshot)
|
||||
mock_client.getSnapshotByName.side_effect =\
|
||||
hpexceptions.HTTPNotFound()
|
||||
# no exception is thrown, just error msg is logged
|
||||
self.driver.delete_snapshot(self.snapshot)
|
||||
|
||||
# mock HTTPServerError because the snap is in use
|
||||
ex = hpexceptions.HTTPServerError({
|
||||
'message':
|
||||
'Hey, dude cannot be deleted because it is a clone point duh.'})
|
||||
mock_client.getSnapshotByName.side_effect = ex
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(
|
||||
exception.SnapshotIsBusy,
|
||||
self.driver.delete_snapshot,
|
||||
self.snapshot)
|
||||
# mock HTTPServerError (array failure)
|
||||
ex = hpexceptions.HTTPServerError({'message': 'Some message.'})
|
||||
mock_client.getSnapshotByName.side_effect = ex
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.driver.delete_snapshot,
|
||||
self.snapshot)
|
||||
|
||||
# mock HTTPServerError because the snap is in use
|
||||
ex = hpexceptions.HTTPServerError({
|
||||
'message':
|
||||
'Hey, dude cannot be deleted because it is a clone point'
|
||||
' duh.'})
|
||||
mock_client.getSnapshotByName.side_effect = ex
|
||||
# ensure the raised exception is a cinder exception
|
||||
self.assertRaises(
|
||||
exception.SnapshotIsBusy,
|
||||
self.driver.delete_snapshot,
|
||||
self.snapshot)
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
|
||||
|
@ -1030,20 +1101,26 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
mock_client.cloneSnapshot.return_value = {
|
||||
'iscsiIqn': self.connector['initiator']}
|
||||
|
||||
# execute create_volume_from_snapshot
|
||||
model_update = self.driver.create_volume_from_snapshot(
|
||||
self.volume, self.snapshot)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
expected_iqn = 'iqn.1993-08.org.debian:01:222 0'
|
||||
expected_location = "10.0.1.6:3260,1 %s" % expected_iqn
|
||||
self.assertEqual(model_update['provider_location'], expected_location)
|
||||
# execute create_volume_from_snapshot
|
||||
model_update = self.driver.create_volume_from_snapshot(
|
||||
self.volume, self.snapshot)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getSnapshotByName('fakeshapshot'),
|
||||
mock.call.cloneSnapshot('fakevolume', 3)]
|
||||
expected_iqn = 'iqn.1993-08.org.debian:01:222 0'
|
||||
expected_location = "10.0.1.6:3260,1 %s" % expected_iqn
|
||||
self.assertEqual(expected_location,
|
||||
model_update['provider_location'])
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getSnapshotByName('fakeshapshot'),
|
||||
mock.call.cloneSnapshot('fakevolume', 3),
|
||||
mock.call.logout()]
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
def test_create_cloned_volume(self):
|
||||
|
||||
|
@ -1053,16 +1130,21 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
|
||||
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
||||
|
||||
# execute create_cloned_volume
|
||||
self.driver.create_cloned_volume(
|
||||
self.cloned_volume, self.volume)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.cloneVolume('clone_volume', 1)]
|
||||
# execute create_cloned_volume
|
||||
self.driver.create_cloned_volume(
|
||||
self.cloned_volume, self.volume)
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.cloneVolume('clone_volume', 1),
|
||||
mock.call.logout()]
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
@mock.patch.object(volume_types, 'get_volume_type')
|
||||
def test_extra_spec_mapping(self, _mock_get_volume_type):
|
||||
|
@ -1147,13 +1229,18 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
volume['host'] = host
|
||||
new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
|
||||
|
||||
self.driver.retype(ctxt, volume, new_type, diff, host)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume')]
|
||||
self.driver.retype(ctxt, volume, new_type, diff, host)
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.logout()]
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
def test_retype_with_only_LH_extra_specs(self):
|
||||
# setup drive with default configuration
|
||||
|
@ -1178,17 +1265,22 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
volume['host'] = host
|
||||
new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
|
||||
|
||||
self.driver.retype(ctxt, volume, new_type, diff, host)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.modifyVolume(
|
||||
1, {
|
||||
'isThinProvisioned': False,
|
||||
'isAdaptiveOptimizationEnabled': True})]
|
||||
self.driver.retype(ctxt, volume, new_type, diff, host)
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.modifyVolume(
|
||||
1, {
|
||||
'isThinProvisioned': False,
|
||||
'isAdaptiveOptimizationEnabled': True}),
|
||||
mock.call.logout()]
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
def test_retype_with_both_extra_specs(self):
|
||||
# setup drive with default configuration
|
||||
|
@ -1213,14 +1305,19 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
volume['host'] = host
|
||||
new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
|
||||
|
||||
self.driver.retype(ctxt, volume, new_type, diff, host)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.modifyVolume(1, {'isThinProvisioned': True})]
|
||||
self.driver.retype(ctxt, volume, new_type, diff, host)
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.modifyVolume(1, {'isThinProvisioned': True}),
|
||||
mock.call.logout()]
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
def test_retype_same_extra_specs(self):
|
||||
# setup drive with default configuration
|
||||
|
@ -1245,16 +1342,21 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
volume['host'] = host
|
||||
new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
|
||||
|
||||
self.driver.retype(ctxt, volume, new_type, diff, host)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.modifyVolume(
|
||||
1,
|
||||
{'isAdaptiveOptimizationEnabled': False})]
|
||||
self.driver.retype(ctxt, volume, new_type, diff, host)
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.modifyVolume(
|
||||
1,
|
||||
{'isAdaptiveOptimizationEnabled': False}),
|
||||
mock.call.logout()]
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
def test_migrate_no_location(self):
|
||||
# setup drive with default configuration
|
||||
|
@ -1262,18 +1364,19 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
mock_client = self.setup_driver()
|
||||
|
||||
host = {'host': self.serverName, 'capabilities': {}}
|
||||
(migrated, update) = self.driver.migrate_volume(
|
||||
None,
|
||||
self.volume,
|
||||
host)
|
||||
self.assertFalse(migrated)
|
||||
|
||||
# only startup code is called
|
||||
mock_client.assert_has_calls(self.driver_startup_call_stack)
|
||||
# and nothing else
|
||||
self.assertEqual(
|
||||
len(self.driver_startup_call_stack),
|
||||
len(mock_client.method_calls))
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
(migrated, update) = self.driver.migrate_volume(
|
||||
None,
|
||||
self.volume,
|
||||
host)
|
||||
self.assertFalse(migrated)
|
||||
|
||||
mock_client.assert_has_calls([])
|
||||
self.assertEqual(0, len(mock_client.method_calls))
|
||||
|
||||
def test_migrate_incorrect_vip(self):
|
||||
# setup drive with default configuration
|
||||
|
@ -1282,7 +1385,8 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
mock_client.getClusterByName.return_value = {
|
||||
"virtualIPAddresses": [{
|
||||
"ipV4Address": "10.10.10.10",
|
||||
"ipV4NetMask": "255.255.240.0"}]}
|
||||
"ipV4NetMask": "255.255.240.0"}],
|
||||
"id": self.cluster_id}
|
||||
|
||||
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
||||
|
||||
|
@ -1293,20 +1397,26 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
host = {
|
||||
'host': self.serverName,
|
||||
'capabilities': {'location_info': location}}
|
||||
(migrated, update) = self.driver.migrate_volume(
|
||||
None,
|
||||
self.volume,
|
||||
host)
|
||||
self.assertFalse(migrated)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getClusterByName('New_CloudCluster')]
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
# and nothing else
|
||||
self.assertEqual(
|
||||
len(expected),
|
||||
len(mock_client.method_calls))
|
||||
(migrated, update) = self.driver.migrate_volume(
|
||||
None,
|
||||
self.volume,
|
||||
host)
|
||||
self.assertFalse(migrated)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getClusterByName('New_CloudCluster'),
|
||||
mock.call.logout()]
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
# and nothing else
|
||||
self.assertEqual(
|
||||
len(expected),
|
||||
len(mock_client.method_calls))
|
||||
|
||||
def test_migrate_with_location(self):
|
||||
# setup drive with default configuration
|
||||
|
@ -1315,7 +1425,8 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
mock_client.getClusterByName.return_value = {
|
||||
"virtualIPAddresses": [{
|
||||
"ipV4Address": "10.10.10.111",
|
||||
"ipV4NetMask": "255.255.240.0"}]}
|
||||
"ipV4NetMask": "255.255.240.0"}],
|
||||
"id": self.cluster_id}
|
||||
|
||||
mock_client.getVolumeByName.return_value = {'id': self.volume_id,
|
||||
'iscsiSessions': None}
|
||||
|
@ -1329,25 +1440,32 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
host = {
|
||||
'host': self.serverName,
|
||||
'capabilities': {'location_info': location}}
|
||||
(migrated, update) = self.driver.migrate_volume(
|
||||
None,
|
||||
self.volume,
|
||||
host)
|
||||
self.assertTrue(migrated)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getClusterByName('New_CloudCluster'),
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.getVolume(
|
||||
1,
|
||||
'fields=snapshots,snapshots[resource[members[name]]]'),
|
||||
mock.call.modifyVolume(1, {'clusterName': 'New_CloudCluster'})]
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
# and nothing else
|
||||
self.assertEqual(
|
||||
len(expected),
|
||||
len(mock_client.method_calls))
|
||||
(migrated, update) = self.driver.migrate_volume(
|
||||
None,
|
||||
self.volume,
|
||||
host)
|
||||
self.assertTrue(migrated)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getClusterByName('New_CloudCluster'),
|
||||
mock.call.logout()] + self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.getVolume(
|
||||
1,
|
||||
'fields=snapshots,snapshots[resource[members[name]]]'),
|
||||
mock.call.modifyVolume(1, {'clusterName': 'New_CloudCluster'}),
|
||||
mock.call.logout()]
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
# and nothing else
|
||||
self.assertEqual(
|
||||
len(expected),
|
||||
len(mock_client.method_calls))
|
||||
|
||||
def test_migrate_with_Snapshots(self):
|
||||
# setup drive with default configuration
|
||||
|
@ -1356,7 +1474,8 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
mock_client.getClusterByName.return_value = {
|
||||
"virtualIPAddresses": [{
|
||||
"ipV4Address": "10.10.10.111",
|
||||
"ipV4NetMask": "255.255.240.0"}]}
|
||||
"ipV4NetMask": "255.255.240.0"}],
|
||||
"id": self.cluster_id}
|
||||
|
||||
mock_client.getVolumeByName.return_value = {
|
||||
'id': self.volume_id,
|
||||
|
@ -1371,24 +1490,31 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
host = {
|
||||
'host': self.serverName,
|
||||
'capabilities': {'location_info': location}}
|
||||
(migrated, update) = self.driver.migrate_volume(
|
||||
None,
|
||||
self.volume,
|
||||
host)
|
||||
self.assertFalse(migrated)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getClusterByName('New_CloudCluster'),
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.getVolume(
|
||||
1,
|
||||
'fields=snapshots,snapshots[resource[members[name]]]')]
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
# and nothing else
|
||||
self.assertEqual(
|
||||
len(expected),
|
||||
len(mock_client.method_calls))
|
||||
(migrated, update) = self.driver.migrate_volume(
|
||||
None,
|
||||
self.volume,
|
||||
host)
|
||||
self.assertFalse(migrated)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getClusterByName('New_CloudCluster'),
|
||||
mock.call.logout()] + self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.getVolume(
|
||||
1,
|
||||
'fields=snapshots,snapshots[resource[members[name]]]'),
|
||||
mock.call.logout()]
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
# and nothing else
|
||||
self.assertEqual(
|
||||
len(expected),
|
||||
len(mock_client.method_calls))
|
||||
|
||||
@mock.patch.object(volume_types, 'get_volume_type',
|
||||
return_value={'extra_specs': {'hplh:ao': 'true'}})
|
||||
|
@ -1405,21 +1531,27 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
mock_client.createVolume.return_value = {
|
||||
'iscsiIqn': self.connector['initiator']}
|
||||
|
||||
volume_info = self.driver.create_volume(volume_with_vt)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
self.assertEqual('10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0',
|
||||
volume_info['provider_location'])
|
||||
volume_info = self.driver.create_volume(volume_with_vt)
|
||||
|
||||
# make sure createVolume is called without
|
||||
# isAdaptiveOptimizationEnabled == true
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.createVolume(
|
||||
'fakevolume',
|
||||
1,
|
||||
units.Gi,
|
||||
{'isThinProvisioned': True, 'clusterName': 'CloudCluster1'})]
|
||||
self.assertEqual('10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0',
|
||||
volume_info['provider_location'])
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
# make sure createVolume is called without
|
||||
# isAdaptiveOptimizationEnabled == true
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.createVolume(
|
||||
'fakevolume',
|
||||
1,
|
||||
units.Gi,
|
||||
{'isThinProvisioned': True,
|
||||
'clusterName': 'CloudCluster1'}),
|
||||
mock.call.logout()]
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
@mock.patch.object(volume_types, 'get_volume_type',
|
||||
return_value={'extra_specs': {'hplh:ao': 'false'}})
|
||||
|
@ -1436,20 +1568,25 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|||
mock_client.createVolume.return_value = {
|
||||
'iscsiIqn': self.connector['initiator']}
|
||||
|
||||
volume_info = self.driver.create_volume(volume_with_vt)
|
||||
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
||||
'_create_client') as mock_do_setup:
|
||||
mock_do_setup.return_value = mock_client
|
||||
|
||||
self.assertEqual('10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0',
|
||||
volume_info['provider_location'])
|
||||
volume_info = self.driver.create_volume(volume_with_vt)
|
||||
|
||||
# make sure createVolume is called with
|
||||
# isAdaptiveOptimizationEnabled == false
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.createVolume(
|
||||
'fakevolume',
|
||||
1,
|
||||
units.Gi,
|
||||
{'isThinProvisioned': True,
|
||||
'clusterName': 'CloudCluster1',
|
||||
'isAdaptiveOptimizationEnabled': False})]
|
||||
self.assertEqual('10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0',
|
||||
volume_info['provider_location'])
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
# make sure createVolume is called with
|
||||
# isAdaptiveOptimizationEnabled == false
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.createVolume(
|
||||
'fakevolume',
|
||||
1,
|
||||
units.Gi,
|
||||
{'isThinProvisioned': True,
|
||||
'clusterName': 'CloudCluster1',
|
||||
'isAdaptiveOptimizationEnabled': False}),
|
||||
mock.call.logout()]
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
|
|
@ -32,15 +32,16 @@ hplefthand_password for credentials to talk to the REST service on the
|
|||
LeftHand array.
|
||||
"""
|
||||
from cinder import exception
|
||||
from cinder.i18n import _LI
|
||||
from cinder.i18n import _LE, _LI
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import utils
|
||||
from cinder.volume.driver import VolumeDriver
|
||||
from cinder.volume.drivers.san.hp import hp_lefthand_cliq_proxy as cliq_proxy
|
||||
from cinder.volume.drivers.san.hp import hp_lefthand_rest_proxy as rest_proxy
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
MIN_CLIENT_VERSION = '1.0.3'
|
||||
|
||||
|
||||
class HPLeftHandISCSIDriver(VolumeDriver):
|
||||
"""Executes commands relating to HP/LeftHand SAN ISCSI volumes.
|
||||
|
@ -50,9 +51,10 @@ class HPLeftHandISCSIDriver(VolumeDriver):
|
|||
1.0.1 - Added support for retype
|
||||
1.0.2 - Added support for volume migrate
|
||||
1.0.3 - Fix for no handler for logger during tests
|
||||
1.0.4 - Removing locks bug #1395953
|
||||
"""
|
||||
|
||||
VERSION = "1.0.3"
|
||||
VERSION = "1.0.4"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HPLeftHandISCSIDriver, self).__init__(*args, **kwargs)
|
||||
|
@ -68,88 +70,85 @@ class HPLeftHandISCSIDriver(VolumeDriver):
|
|||
|
||||
return proxy
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def check_for_setup_error(self):
|
||||
self.proxy.check_for_setup_error()
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def do_setup(self, context):
|
||||
self.proxy = self._create_proxy(*self.args, **self.kwargs)
|
||||
self.proxy.do_setup(context)
|
||||
|
||||
LOG.info(_LI("HPLeftHand driver %(driver_ver)s, "
|
||||
"proxy %(proxy_ver)s") % {
|
||||
"driver_ver": self.VERSION,
|
||||
"proxy_ver": self.proxy.get_version_string()})
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
if isinstance(self.proxy, cliq_proxy.HPLeftHandCLIQProxy):
|
||||
self.proxy.do_setup(context)
|
||||
else:
|
||||
# Check minimum client version for REST proxy
|
||||
client_version = rest_proxy.hplefthandclient.version
|
||||
|
||||
if (client_version < MIN_CLIENT_VERSION):
|
||||
ex_msg = (_LE("Invalid hplefthandclient version found ("
|
||||
"%(found)s). Version %(minimum)s or greater "
|
||||
"required.")
|
||||
% {'found': client_version,
|
||||
'minimum': MIN_CLIENT_VERSION})
|
||||
LOG.error(ex_msg)
|
||||
raise exception.InvalidInput(reason=ex_msg)
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Creates a volume."""
|
||||
return self.proxy.create_volume(volume)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend the size of an existing volume."""
|
||||
self.proxy.extend_volume(volume, new_size)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from a snapshot."""
|
||||
return self.proxy.create_volume_from_snapshot(volume, snapshot)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Creates a snapshot."""
|
||||
self.proxy.create_snapshot(snapshot)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a volume."""
|
||||
self.proxy.delete_volume(volume)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a snapshot."""
|
||||
self.proxy.delete_snapshot(snapshot)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Assigns the volume to a server."""
|
||||
return self.proxy.initialize_connection(volume, connector)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Unassign the volume from the host."""
|
||||
self.proxy.terminate_connection(volume, connector)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def get_volume_stats(self, refresh):
|
||||
data = self.proxy.get_volume_stats(refresh)
|
||||
data['driver_version'] = self.VERSION
|
||||
return data
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
return self.proxy.create_cloned_volume(volume, src_vref)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def create_export(self, context, volume):
|
||||
return self.proxy.create_export(context, volume)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def ensure_export(self, context, volume):
|
||||
return self.proxy.ensure_export(context, volume)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def remove_export(self, context, volume):
|
||||
return self.proxy.remove_export(context, volume)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def retype(self, context, volume, new_type, diff, host):
|
||||
"""Convert the volume to be of the new type."""
|
||||
return self.proxy.retype(context, volume, new_type, diff, host)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def migrate_volume(self, ctxt, volume, host):
|
||||
"""Migrate directly if source and dest are managed by same storage."""
|
||||
return self.proxy.migrate_volume(ctxt, volume, host)
|
||||
|
|
|
@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__)
|
|||
|
||||
try:
|
||||
import hplefthandclient
|
||||
from hplefthandclient import client
|
||||
from hplefthandclient import client as hp_lh_client
|
||||
from hplefthandclient import exceptions as hpexceptions
|
||||
except ImportError:
|
||||
import cinder.tests.fake_hp_lefthand_client as hplefthandclient
|
||||
|
@ -93,9 +93,10 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
improvement
|
||||
1.0.5 - Fixed bug #1311350, Live-migration of an instance when
|
||||
attached to a volume was causing an error.
|
||||
1.0.6 - Removing locks bug #1395953
|
||||
"""
|
||||
|
||||
VERSION = "1.0.5"
|
||||
VERSION = "1.0.6"
|
||||
|
||||
device_stats = {}
|
||||
|
||||
|
@ -109,24 +110,36 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
# so we need to use it as a separator
|
||||
self.DRIVER_LOCATION = self.__class__.__name__ + ' %(cluster)s %(vip)s'
|
||||
|
||||
def _login(self):
|
||||
client = self.do_setup(None)
|
||||
return client
|
||||
|
||||
def _logout(self, client):
|
||||
client.logout()
|
||||
|
||||
def _create_client(self):
|
||||
return hp_lh_client.HPLeftHandClient(
|
||||
self.configuration.hplefthand_api_url)
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Set up LeftHand client."""
|
||||
try:
|
||||
self.client = client.HPLeftHandClient(
|
||||
self.configuration.hplefthand_api_url)
|
||||
self.client.login(
|
||||
client = self._create_client()
|
||||
client.login(
|
||||
self.configuration.hplefthand_username,
|
||||
self.configuration.hplefthand_password)
|
||||
|
||||
if self.configuration.hplefthand_debug:
|
||||
self.client.debug_rest(True)
|
||||
client.debug_rest(True)
|
||||
|
||||
cluster_info = self.client.getClusterByName(
|
||||
cluster_info = client.getClusterByName(
|
||||
self.configuration.hplefthand_clustername)
|
||||
self.cluster_id = cluster_info['id']
|
||||
virtual_ips = cluster_info['virtualIPAddresses']
|
||||
self.cluster_vip = virtual_ips[0]['ipV4Address']
|
||||
self._update_backend_status()
|
||||
self._update_backend_status(client)
|
||||
|
||||
return client
|
||||
except hpexceptions.HTTPNotFound:
|
||||
raise exception.DriverNotInitialized(
|
||||
_('LeftHand cluster not found'))
|
||||
|
@ -143,6 +156,7 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
|
||||
def create_volume(self, volume):
|
||||
"""Creates a volume."""
|
||||
client = self._login()
|
||||
try:
|
||||
# get the extra specs of interest from this volume's volume type
|
||||
volume_extra_specs = self._get_volume_extra_specs(volume)
|
||||
|
@ -170,7 +184,7 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
clusterName = self.configuration.hplefthand_clustername
|
||||
optional['clusterName'] = clusterName
|
||||
|
||||
volume_info = self.client.createVolume(
|
||||
volume_info = client.createVolume(
|
||||
volume['name'], self.cluster_id,
|
||||
volume['size'] * units.Gi,
|
||||
optional)
|
||||
|
@ -178,45 +192,57 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
return self._update_provider(volume_info)
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(ex)
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a volume."""
|
||||
client = self._login()
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(volume['name'])
|
||||
self.client.deleteVolume(volume_info['id'])
|
||||
volume_info = client.getVolumeByName(volume['name'])
|
||||
client.deleteVolume(volume_info['id'])
|
||||
except hpexceptions.HTTPNotFound:
|
||||
LOG.error(_LE("Volume did not exist. It will not be deleted"))
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(ex)
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend the size of an existing volume."""
|
||||
client = self._login()
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(volume['name'])
|
||||
volume_info = client.getVolumeByName(volume['name'])
|
||||
|
||||
# convert GB to bytes
|
||||
options = {'size': int(new_size) * units.Gi}
|
||||
self.client.modifyVolume(volume_info['id'], options)
|
||||
client.modifyVolume(volume_info['id'], options)
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(ex)
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Creates a snapshot."""
|
||||
client = self._login()
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(snapshot['volume_name'])
|
||||
volume_info = client.getVolumeByName(snapshot['volume_name'])
|
||||
|
||||
option = {'inheritAccess': True}
|
||||
self.client.createSnapshot(snapshot['name'],
|
||||
volume_info['id'],
|
||||
option)
|
||||
client.createSnapshot(snapshot['name'],
|
||||
volume_info['id'],
|
||||
option)
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(ex)
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a snapshot."""
|
||||
client = self._login()
|
||||
try:
|
||||
snap_info = self.client.getSnapshotByName(snapshot['name'])
|
||||
self.client.deleteSnapshot(snap_info['id'])
|
||||
snap_info = client.getSnapshotByName(snapshot['name'])
|
||||
client.deleteSnapshot(snap_info['id'])
|
||||
except hpexceptions.HTTPNotFound:
|
||||
LOG.error(_LE("Snapshot did not exist. It will not be deleted"))
|
||||
except hpexceptions.HTTPServerError as ex:
|
||||
|
@ -228,15 +254,21 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(ex)
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
def get_volume_stats(self, refresh):
|
||||
"""Gets volume stats."""
|
||||
if refresh:
|
||||
self._update_backend_status()
|
||||
client = self._login()
|
||||
try:
|
||||
if refresh:
|
||||
self._update_backend_status(client)
|
||||
|
||||
return self.device_stats
|
||||
return self.device_stats
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
def _update_backend_status(self):
|
||||
def _update_backend_status(self, client):
|
||||
data = {}
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
data['volume_backend_name'] = backend_name or self.__class__.__name__
|
||||
|
@ -247,7 +279,7 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
'cluster': self.configuration.hplefthand_clustername,
|
||||
'vip': self.cluster_vip})
|
||||
|
||||
cluster_info = self.client.getCluster(self.cluster_id)
|
||||
cluster_info = client.getCluster(self.cluster_id)
|
||||
|
||||
total_capacity = cluster_info['spaceTotal']
|
||||
free_capacity = cluster_info['spaceAvailable']
|
||||
|
@ -265,9 +297,10 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
used from that host. HP VSA requires a volume to be assigned
|
||||
to a server.
|
||||
"""
|
||||
client = self._login()
|
||||
try:
|
||||
server_info = self._create_server(connector)
|
||||
volume_info = self.client.getVolumeByName(volume['name'])
|
||||
server_info = self._create_server(connector, client)
|
||||
volume_info = client.getVolumeByName(volume['name'])
|
||||
|
||||
access_already_enabled = False
|
||||
if volume_info['iscsiSessions'] is not None:
|
||||
|
@ -280,7 +313,7 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
break
|
||||
|
||||
if not access_already_enabled:
|
||||
self.client.addServerAccess(
|
||||
client.addServerAccess(
|
||||
volume_info['id'],
|
||||
server_info['id'])
|
||||
|
||||
|
@ -296,35 +329,46 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
return {'driver_volume_type': 'iscsi', 'data': iscsi_properties}
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(ex)
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Unassign the volume from the host."""
|
||||
client = self._login()
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(volume['name'])
|
||||
server_info = self.client.getServerByName(connector['host'])
|
||||
self.client.removeServerAccess(
|
||||
volume_info = client.getVolumeByName(volume['name'])
|
||||
server_info = client.getServerByName(connector['host'])
|
||||
client.removeServerAccess(
|
||||
volume_info['id'],
|
||||
server_info['id'])
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(ex)
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from a snapshot."""
|
||||
client = self._login()
|
||||
try:
|
||||
snap_info = self.client.getSnapshotByName(snapshot['name'])
|
||||
volume_info = self.client.cloneSnapshot(
|
||||
snap_info = client.getSnapshotByName(snapshot['name'])
|
||||
volume_info = client.cloneSnapshot(
|
||||
volume['name'],
|
||||
snap_info['id'])
|
||||
return self._update_provider(volume_info)
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(ex)
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
client = self._login()
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(src_vref['name'])
|
||||
self.client.cloneVolume(volume['name'], volume_info['id'])
|
||||
volume_info = client.getVolumeByName(src_vref['name'])
|
||||
client.cloneVolume(volume['name'], volume_info['id'])
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(ex)
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
def _get_volume_extra_specs(self, volume):
|
||||
"""Get extra specs from a volume."""
|
||||
|
@ -370,11 +414,11 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
return {'provider_location': (
|
||||
"%s %s %s" % (iscsi_portal, volume_info['iscsiIqn'], 0))}
|
||||
|
||||
def _create_server(self, connector):
|
||||
def _create_server(self, connector, client):
|
||||
server_info = None
|
||||
chap_enabled = self.configuration.hplefthand_iscsi_chap_enabled
|
||||
try:
|
||||
server_info = self.client.getServerByName(connector['host'])
|
||||
server_info = client.getServerByName(connector['host'])
|
||||
chap_secret = server_info['chapTargetSecret']
|
||||
if not chap_enabled and chap_secret:
|
||||
LOG.warning(_LW('CHAP secret exists for host %s but CHAP is '
|
||||
|
@ -394,9 +438,10 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
'chapTargetSecret': chap_secret,
|
||||
'chapAuthenticationRequired': True
|
||||
}
|
||||
server_info = self.client.createServer(connector['host'],
|
||||
connector['initiator'],
|
||||
optional)
|
||||
|
||||
server_info = client.createServer(connector['host'],
|
||||
connector['initiator'],
|
||||
optional)
|
||||
return server_info
|
||||
|
||||
def create_export(self, context, volume):
|
||||
|
@ -426,12 +471,10 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
'new_type': new_type,
|
||||
'diff': diff,
|
||||
'host': host})
|
||||
client = self._login()
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(volume['name'])
|
||||
except hpexceptions.HTTPNotFound:
|
||||
raise exception.VolumeNotFound(volume_id=volume['id'])
|
||||
volume_info = client.getVolumeByName(volume['name'])
|
||||
|
||||
try:
|
||||
# pick out the LH extra specs
|
||||
new_extra_specs = dict(new_type).get('extra_specs')
|
||||
lh_extra_specs = self._get_lh_extra_specs(
|
||||
|
@ -450,11 +493,14 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
# map extra specs to LeftHand options
|
||||
options = self._map_extra_specs(changed_extra_specs)
|
||||
if len(options) > 0:
|
||||
self.client.modifyVolume(volume_info['id'], options)
|
||||
client.modifyVolume(volume_info['id'], options)
|
||||
return True
|
||||
|
||||
except hpexceptions.HTTPNotFound:
|
||||
raise exception.VolumeNotFound(volume_id=volume['id'])
|
||||
except Exception as ex:
|
||||
LOG.warning("%s" % ex)
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
return False
|
||||
|
||||
|
@ -491,10 +537,11 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
|
||||
host_location = host['capabilities']['location_info']
|
||||
(driver, cluster, vip) = host_location.split(' ')
|
||||
client = self._login()
|
||||
try:
|
||||
# get the cluster info, if it exists and compare
|
||||
cluster_info = self.client.getClusterByName(cluster)
|
||||
LOG.debug('Clister info: %s' % cluster_info)
|
||||
cluster_info = client.getClusterByName(cluster)
|
||||
LOG.debug('Cluster info: %s' % cluster_info)
|
||||
virtual_ips = cluster_info['virtualIPAddresses']
|
||||
|
||||
if driver != self.__class__.__name__:
|
||||
|
@ -513,9 +560,12 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
"volume: %s because cluster exists in different "
|
||||
"management group.") % volume['name'])
|
||||
return false_ret
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
client = self._login()
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(volume['name'])
|
||||
volume_info = client.getVolumeByName(volume['name'])
|
||||
LOG.debug('Volume info: %s' % volume_info)
|
||||
|
||||
# can't migrate if server is attached
|
||||
|
@ -526,7 +576,7 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
return false_ret
|
||||
|
||||
# can't migrate if volume has snapshots
|
||||
snap_info = self.client.getVolume(
|
||||
snap_info = client.getVolume(
|
||||
volume_info['id'],
|
||||
'fields=snapshots,snapshots[resource[members[name]]]')
|
||||
LOG.debug('Snapshot info: %s' % snap_info)
|
||||
|
@ -537,7 +587,7 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
return false_ret
|
||||
|
||||
options = {'clusterName': cluster}
|
||||
self.client.modifyVolume(volume_info['id'], options)
|
||||
client.modifyVolume(volume_info['id'], options)
|
||||
except hpexceptions.HTTPNotFound:
|
||||
LOG.info(_LI("Cannot provide backend assisted migration for "
|
||||
"volume: %s because volume does not exist in this "
|
||||
|
@ -546,5 +596,7 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
|||
except hpexceptions.HTTPServerError as ex:
|
||||
LOG.error(ex)
|
||||
return false_ret
|
||||
finally:
|
||||
self._logout(client)
|
||||
|
||||
return (True, None)
|
||||
|
|
Loading…
Reference in New Issue