Hitachi: Prevent to delete a LDEV assigned to multi objects
This patch prevents to delete a LDEV that is unexpectedly assigned to two or more objects(volumes or snapshots). In the unexpected situation, if ``delete`` command for one of objects is run again, the data which is used by other objects is lost. In order to prevent the data-loss, when creating an object, the driver creates a LDEV and stores a value obtained by omitting the hyphen from the object ID(*1) to ``LDEV nickname``. When deleting an object, the driver compares the own object ID and the object ID in ``LDEV nickname``, then, the object and the LDEV is deleted only if both object IDs are same. On the other hand, if both object IDs are not same, only the object is deleted and the LDEV is kept, to prevent data-loss. If format of ``LDEV nickname`` is not object ID(*2), both the object and the LDEV is deleted without comparison, because it avoids disk full risk, due to not deleting any LDEVs. This patch implements only the object ID storing while creating a snapshot and comparing IDs while deleting, because the feature to store the object ID while creating a volume has already been implemented. (*1) Max length of ``LDEV nickname`` is 32 digits characters on Hitachi storage. (*2) 32 digits hexadecimal Closes-Bug: #2072317 Change-Id: I7c6bd9a75dd1d7165d4f8614abb3d59fa642212d
This commit is contained in:
parent
30cdc86784
commit
d04db6fe88
@ -271,6 +271,29 @@ GET_LDEV_RESULT = {
|
||||
"poolId": 30,
|
||||
"dataReductionStatus": "DISABLED",
|
||||
"dataReductionMode": "disabled",
|
||||
"label": "00000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_SPLIT = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "HDP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"dataReductionStatus": "DISABLED",
|
||||
"dataReductionMode": "disabled",
|
||||
"label": "00000000000000000000000000000004",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_LABEL = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "HDP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"dataReductionStatus": "DISABLED",
|
||||
"dataReductionMode": "disabled",
|
||||
"label": "00000000000000000000000000000001",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_MAPPED = {
|
||||
@ -308,6 +331,7 @@ GET_LDEV_RESULT_PAIR = {
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "HDP", "HTI"],
|
||||
"status": "NML",
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_REP = {
|
||||
@ -316,6 +340,16 @@ GET_LDEV_RESULT_REP = {
|
||||
"attributes": ["CVS", "HDP", "GAD"],
|
||||
"status": "NML",
|
||||
"numOfPorts": 1,
|
||||
"label": "00000000000000000000000000000004",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_REP_LABEL = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "HDP", "GAD"],
|
||||
"status": "NML",
|
||||
"numOfPorts": 1,
|
||||
"label": "00000000000000000000000000000001",
|
||||
}
|
||||
|
||||
GET_POOL_RESULT = {
|
||||
@ -889,19 +923,42 @@ class HBSDMIRRORFCDriverTest(test.TestCase):
|
||||
self.ldev_count = self.ldev_count + 1
|
||||
return FakeResponse(200, GET_LDEV_RESULT_REP)
|
||||
else:
|
||||
return FakeResponse(200, GET_LDEV_RESULT)
|
||||
return FakeResponse(200, GET_LDEV_RESULT_SPLIT)
|
||||
else:
|
||||
if method in ('POST', 'PUT', 'DELETE'):
|
||||
return FakeResponse(202, REMOTE_COMPLETED_SUCCEEDED_RESULT)
|
||||
elif method == 'GET':
|
||||
if '/ldevs/' in url:
|
||||
return FakeResponse(200, GET_LDEV_RESULT)
|
||||
return FakeResponse(200, GET_LDEV_RESULT_SPLIT)
|
||||
return FakeResponse(
|
||||
500, ERROR_RESULT, headers={'Content-Type': 'json'})
|
||||
request.side_effect = _request_side_effect
|
||||
self.driver.delete_volume(TEST_VOLUME[4])
|
||||
self.assertEqual(17, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_volume_primary_is_invalid_ldev(self, request):
|
||||
request.return_value = FakeResponse(200, GET_LDEV_RESULT_LABEL)
|
||||
self.driver.delete_volume(TEST_VOLUME[0])
|
||||
self.assertEqual(1, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_volume_primary_secondary_is_invalid_ldev(self, request):
|
||||
request.return_value = FakeResponse(200, GET_LDEV_RESULT_REP_LABEL)
|
||||
self.driver.delete_volume(TEST_VOLUME[4])
|
||||
self.assertEqual(2, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_volume_secondary_is_invalid_ldev(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_REP_LABEL),
|
||||
FakeResponse(200, GET_LDEV_RESULT_REP),
|
||||
FakeResponse(200, GET_LDEV_RESULT_REP),
|
||||
FakeResponse(200, GET_LDEV_RESULT_REP),
|
||||
FakeResponse(200, GET_LDEV_RESULT_REP),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.delete_volume(TEST_VOLUME[4])
|
||||
self.assertEqual(6, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_extend_volume(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
@ -979,7 +1036,8 @@ class HBSDMIRRORFCDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common.rep_primary._stats = {}
|
||||
self.driver.common.rep_primary._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
@ -989,7 +1047,7 @@ class HBSDMIRRORFCDriverTest(test.TestCase):
|
||||
ret = self.driver.create_snapshot(TEST_SNAPSHOT[0])
|
||||
actual = {'provider_location': '1'}
|
||||
self.assertEqual(actual, ret)
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_snapshot(self, request):
|
||||
@ -1286,41 +1344,25 @@ class HBSDMIRRORFCDriverTest(test.TestCase):
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_update_migrated_volume(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.driver.update_migrated_volume,
|
||||
self.ctxt,
|
||||
TEST_VOLUME[0],
|
||||
TEST_VOLUME[1],
|
||||
"available")
|
||||
self.assertEqual(4, request.call_count)
|
||||
args, kwargs = request.call_args_list[3]
|
||||
self.assertEqual(kwargs['json']['label'],
|
||||
TEST_VOLUME[0]['id'].replace("-", ""))
|
||||
ret = self.driver.update_migrated_volume(
|
||||
self.ctxt, TEST_VOLUME[0], TEST_VOLUME[1], "available")
|
||||
self.assertEqual(2, request.call_count)
|
||||
actual = ({'_name_id': TEST_VOLUME[1]['id'],
|
||||
'provider_location': TEST_VOLUME[1]['provider_location']})
|
||||
self.assertEqual(actual, ret)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_update_migrated_volume_replication(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_REP),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT_REP),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.driver.update_migrated_volume,
|
||||
self.ctxt,
|
||||
TEST_VOLUME[0],
|
||||
TEST_VOLUME[4],
|
||||
"available")
|
||||
self.assertEqual(6, request.call_count)
|
||||
original_volume_nickname = TEST_VOLUME[0]['id'].replace("-", "")
|
||||
for i in (4, 5):
|
||||
args, kwargs = request.call_args_list[i]
|
||||
self.assertEqual(kwargs['json']['label'], original_volume_nickname)
|
||||
ret = self.driver.update_migrated_volume(
|
||||
self.ctxt, TEST_VOLUME[0], TEST_VOLUME[4], "available")
|
||||
self.assertEqual(3, request.call_count)
|
||||
actual = ({'_name_id': TEST_VOLUME[4]['id'],
|
||||
'provider_location': TEST_VOLUME[4]['provider_location']})
|
||||
self.assertEqual(actual, ret)
|
||||
|
||||
def test_unmanage_snapshot(self):
|
||||
"""The driver don't support unmange_snapshot."""
|
||||
@ -1470,6 +1512,39 @@ class HBSDMIRRORFCDriverTest(test.TestCase):
|
||||
'provider_location': '1'}])
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(volume_types, 'get_volume_type')
|
||||
@mock.patch.object(volume_types, 'get_volume_type_extra_specs')
|
||||
def test_create_group_from_src_Exception(
|
||||
self, get_volume_type_extra_specs, get_volume_type, request):
|
||||
extra_specs = {"test1": "aaa"}
|
||||
get_volume_type_extra_specs.return_value = extra_specs
|
||||
get_volume_type.return_value = {}
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common.rep_primary._stats = {}
|
||||
self.driver.common.rep_primary._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
self.driver.common.rep_secondary._stats = {}
|
||||
self.driver.common.rep_secondary._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 40}}]
|
||||
self.assertRaises(exception.VolumeDriverException,
|
||||
self.driver.create_group_from_src,
|
||||
self.ctxt, TEST_GROUP[1],
|
||||
[TEST_VOLUME[1], TEST_VOLUME[1]],
|
||||
source_group=TEST_GROUP[0],
|
||||
source_vols=[TEST_VOLUME[0], TEST_VOLUME[3]]
|
||||
)
|
||||
self.assertEqual(10, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(volume_types, 'get_volume_type')
|
||||
@mock.patch.object(volume_types, 'get_volume_type_extra_specs')
|
||||
@ -1520,7 +1595,8 @@ class HBSDMIRRORFCDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common.rep_primary._stats = {}
|
||||
self.driver.common.rep_primary._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
@ -1530,7 +1606,7 @@ class HBSDMIRRORFCDriverTest(test.TestCase):
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
{'status': 'available'},
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
|
@ -219,6 +219,29 @@ GET_LDEV_RESULT = {
|
||||
"poolId": 30,
|
||||
"dataReductionStatus": "DISABLED",
|
||||
"dataReductionMode": "disabled",
|
||||
"label": "00000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_LABEL = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "HDP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"dataReductionStatus": "DISABLED",
|
||||
"dataReductionMode": "disabled",
|
||||
"label": "00000000000000000000000000000001",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_SNAP = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "HDP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"dataReductionStatus": "DISABLED",
|
||||
"dataReductionMode": "disabled",
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_MAPPED = {
|
||||
@ -241,6 +264,15 @@ GET_LDEV_RESULT_PAIR = {
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "HDP", "HTI"],
|
||||
"status": "NML",
|
||||
"label": "00000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_PAIR_SNAP = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "HDP", "HTI"],
|
||||
"status": "NML",
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_PAIR_TEST = {
|
||||
@ -872,6 +904,12 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
TEST_VOLUME[0])
|
||||
self.assertEqual(2, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_volume_is_invalid_ldev(self, request):
|
||||
request.return_value = FakeResponse(200, GET_LDEV_RESULT_LABEL)
|
||||
self.driver.delete_volume(TEST_VOLUME[0])
|
||||
self.assertEqual(1, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_extend_volume(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
@ -952,7 +990,8 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
get_volume_type_extra_specs.return_value = {}
|
||||
self.driver.common._stats = {}
|
||||
self.driver.common._stats['pools'] = [
|
||||
@ -960,7 +999,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
ret = self.driver.create_snapshot(TEST_SNAPSHOT[0])
|
||||
self.assertEqual('1', ret['provider_location'])
|
||||
self.assertEqual(1, get_volume_type_extra_specs.call_count)
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(volume_types, 'get_volume_type_extra_specs')
|
||||
@ -970,7 +1009,8 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
get_volume_type_extra_specs.return_value = {'hbsd:capacity_saving':
|
||||
'disable'}
|
||||
self.driver.common._stats = {}
|
||||
@ -979,11 +1019,11 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
ret = self.driver.create_snapshot(TEST_SNAPSHOT[0])
|
||||
self.assertEqual('1', ret['provider_location'])
|
||||
self.assertEqual(1, get_volume_type_extra_specs.call_count)
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR_SNAP),
|
||||
FakeResponse(200, NOTFOUND_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
@ -1003,7 +1043,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_snapshot_no_pair(self, request):
|
||||
"""Normal case: Delete a snapshot without pair."""
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_SNAP),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
@ -1298,17 +1338,12 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_update_migrated_volume(self, request):
|
||||
request.return_value = FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.driver.update_migrated_volume,
|
||||
self.ctxt,
|
||||
TEST_VOLUME[0],
|
||||
TEST_VOLUME[1],
|
||||
"available")
|
||||
self.assertEqual(2, request.call_count)
|
||||
args, kwargs = request.call_args_list[1]
|
||||
self.assertEqual(kwargs['json']['label'],
|
||||
TEST_VOLUME[0]['id'].replace("-", ""))
|
||||
ret = self.driver.update_migrated_volume(
|
||||
self.ctxt, TEST_VOLUME[0], TEST_VOLUME[1], "available")
|
||||
self.assertEqual(1, request.call_count)
|
||||
actual = ({'_name_id': TEST_VOLUME[1]['id'],
|
||||
'provider_location': TEST_VOLUME[1]['provider_location']})
|
||||
self.assertEqual(actual, ret)
|
||||
|
||||
def test_unmanage_snapshot(self):
|
||||
"""The driver don't support unmange_snapshot."""
|
||||
@ -1512,7 +1547,8 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common._stats = {}
|
||||
self.driver.common._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
@ -1520,7 +1556,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(1, get_volume_type_extra_specs.call_count)
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
{'status': 'available'},
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
@ -1539,6 +1575,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
is_group_a_cg_snapshot_type.return_value = True
|
||||
get_volume_type_extra_specs.return_value = {}
|
||||
request.side_effect = [FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT_PAIR),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
@ -1550,7 +1587,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(1, get_volume_type_extra_specs.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
self.assertEqual(6, request.call_count)
|
||||
actual = (
|
||||
None,
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
@ -1561,7 +1598,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_group_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR_SNAP),
|
||||
FakeResponse(200, NOTFOUND_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
|
@ -194,6 +194,18 @@ GET_LDEV_RESULT = {
|
||||
"poolId": 30,
|
||||
"dataReductionStatus": "DISABLED",
|
||||
"dataReductionMode": "disabled",
|
||||
"label": "00000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_SNAP = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "HDP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"dataReductionStatus": "DISABLED",
|
||||
"dataReductionMode": "disabled",
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_MAPPED = {
|
||||
@ -216,6 +228,7 @@ GET_LDEV_RESULT_PAIR = {
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "HDP", "HTI"],
|
||||
"status": "NML",
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_POOLS_RESULT = {
|
||||
@ -628,24 +641,31 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common._stats = {}
|
||||
self.driver.common._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
ret = self.driver.create_snapshot(TEST_SNAPSHOT[0])
|
||||
self.assertEqual('1', ret['provider_location'])
|
||||
self.assertEqual(1, get_volume_type_extra_specs.call_count)
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_SNAP),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.delete_snapshot(TEST_SNAPSHOT[0])
|
||||
self.assertEqual(4, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_snapshot_is_invalid_ldev(self, request):
|
||||
request.return_value = FakeResponse(200, GET_LDEV_RESULT)
|
||||
self.driver.delete_snapshot(TEST_SNAPSHOT[0])
|
||||
self.assertEqual(1, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(volume_types, 'get_volume_type_extra_specs')
|
||||
def test_create_cloned_volume(
|
||||
@ -860,17 +880,12 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_update_migrated_volume(self, request):
|
||||
request.return_value = FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.driver.update_migrated_volume,
|
||||
self.ctxt,
|
||||
TEST_VOLUME[0],
|
||||
TEST_VOLUME[1],
|
||||
"available")
|
||||
self.assertEqual(2, request.call_count)
|
||||
args, kwargs = request.call_args_list[1]
|
||||
self.assertEqual(kwargs['json']['label'],
|
||||
TEST_VOLUME[0]['id'].replace("-", ""))
|
||||
ret = self.driver.update_migrated_volume(
|
||||
self.ctxt, TEST_VOLUME[0], TEST_VOLUME[1], "available")
|
||||
self.assertEqual(1, request.call_count)
|
||||
actual = ({'_name_id': TEST_VOLUME[1]['id'],
|
||||
'provider_location': TEST_VOLUME[1]['provider_location']})
|
||||
self.assertEqual(actual, ret)
|
||||
|
||||
def test_unmanage_snapshot(self):
|
||||
"""The driver don't support unmange_snapshot."""
|
||||
@ -1034,7 +1049,8 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common._stats = {}
|
||||
self.driver.common._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
@ -1042,7 +1058,7 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(1, get_volume_type_extra_specs.call_count)
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
{'status': 'available'},
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
@ -1061,6 +1077,7 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
|
||||
is_group_a_cg_snapshot_type.return_value = True
|
||||
get_volume_type_extra_specs.return_value = {}
|
||||
request.side_effect = [FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT_PAIR),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
@ -1072,7 +1089,7 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(1, get_volume_type_extra_specs.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
self.assertEqual(6, request.call_count)
|
||||
actual = (
|
||||
None,
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
|
@ -187,6 +187,7 @@ GET_LDEV_RESULT = {
|
||||
"attributes": ["CVS", "THP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"label": "00000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_MAPPED = {
|
||||
@ -204,11 +205,29 @@ GET_LDEV_RESULT_MAPPED = {
|
||||
],
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_SNAP = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "THP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_PAIR = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "THP", "FS"],
|
||||
"status": "NML",
|
||||
"label": "00000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_PAIR_SNAP = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "THP", "FS"],
|
||||
"status": "NML",
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_POOL_RESULT = {
|
||||
@ -701,17 +720,18 @@ class HPEXPRESTFCDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common._stats = {}
|
||||
self.driver.common._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
ret = self.driver.create_snapshot(TEST_SNAPSHOT[0])
|
||||
self.assertEqual('1', ret['provider_location'])
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR_SNAP),
|
||||
FakeResponse(200, NOTFOUND_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
@ -727,7 +747,7 @@ class HPEXPRESTFCDriverTest(test.TestCase):
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_snapshot_no_pair(self, request):
|
||||
"""Normal case: Delete a snapshot without pair."""
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_SNAP),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
@ -948,17 +968,12 @@ class HPEXPRESTFCDriverTest(test.TestCase):
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_update_migrated_volume(self, request):
|
||||
request.return_value = FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.driver.update_migrated_volume,
|
||||
self.ctxt,
|
||||
TEST_VOLUME[0],
|
||||
TEST_VOLUME[1],
|
||||
"available")
|
||||
self.assertEqual(2, request.call_count)
|
||||
args, kwargs = request.call_args_list[1]
|
||||
self.assertEqual(kwargs['json']['label'],
|
||||
TEST_VOLUME[0]['id'].replace("-", ""))
|
||||
ret = self.driver.update_migrated_volume(
|
||||
self.ctxt, TEST_VOLUME[0], TEST_VOLUME[1], "available")
|
||||
self.assertEqual(1, request.call_count)
|
||||
actual = ({'_name_id': TEST_VOLUME[1]['id'],
|
||||
'provider_location': TEST_VOLUME[1]['provider_location']})
|
||||
self.assertEqual(actual, ret)
|
||||
|
||||
def test_unmanage_snapshot(self):
|
||||
"""The driver don't support unmange_snapshot."""
|
||||
@ -1095,14 +1110,15 @@ class HPEXPRESTFCDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common._stats = {}
|
||||
self.driver.common._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
{'status': 'available'},
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
@ -1118,6 +1134,7 @@ class HPEXPRESTFCDriverTest(test.TestCase):
|
||||
self, is_group_a_cg_snapshot_type, volume_get, request):
|
||||
is_group_a_cg_snapshot_type.return_value = True
|
||||
request.side_effect = [FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT_PAIR),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
@ -1128,7 +1145,7 @@ class HPEXPRESTFCDriverTest(test.TestCase):
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(5, request.call_count)
|
||||
self.assertEqual(6, request.call_count)
|
||||
actual = (
|
||||
None,
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
@ -1139,7 +1156,7 @@ class HPEXPRESTFCDriverTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_group_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR_SNAP),
|
||||
FakeResponse(200, NOTFOUND_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
|
@ -190,6 +190,7 @@ GET_LDEV_RESULT = {
|
||||
"attributes": ["CVS", "THP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"label": "00000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_MAPPED = {
|
||||
@ -207,11 +208,21 @@ GET_LDEV_RESULT_MAPPED = {
|
||||
],
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_SNAP = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "THP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_PAIR = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "THP", "FS"],
|
||||
"status": "NML",
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_POOLS_RESULT = {
|
||||
@ -548,17 +559,18 @@ class HPEXPRESTISCSIDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common._stats = {}
|
||||
self.driver.common._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
ret = self.driver.create_snapshot(TEST_SNAPSHOT[0])
|
||||
self.assertEqual('1', ret['provider_location'])
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_SNAP),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
@ -761,17 +773,12 @@ class HPEXPRESTISCSIDriverTest(test.TestCase):
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_update_migrated_volume(self, request):
|
||||
request.return_value = FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.driver.update_migrated_volume,
|
||||
self.ctxt,
|
||||
TEST_VOLUME[0],
|
||||
TEST_VOLUME[1],
|
||||
"available")
|
||||
self.assertEqual(2, request.call_count)
|
||||
args, kwargs = request.call_args_list[1]
|
||||
self.assertEqual(kwargs['json']['label'],
|
||||
TEST_VOLUME[0]['id'].replace("-", ""))
|
||||
ret = self.driver.update_migrated_volume(
|
||||
self.ctxt, TEST_VOLUME[0], TEST_VOLUME[1], "available")
|
||||
self.assertEqual(1, request.call_count)
|
||||
actual = ({'_name_id': TEST_VOLUME[1]['id'],
|
||||
'provider_location': TEST_VOLUME[1]['provider_location']})
|
||||
self.assertEqual(actual, ret)
|
||||
|
||||
def test_unmanage_snapshot(self):
|
||||
"""The driver don't support unmange_snapshot."""
|
||||
@ -902,14 +909,15 @@ class HPEXPRESTISCSIDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common._stats = {}
|
||||
self.driver.common._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
{'status': 'available'},
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
@ -925,6 +933,7 @@ class HPEXPRESTISCSIDriverTest(test.TestCase):
|
||||
self, is_group_a_cg_snapshot_type, volume_get, request):
|
||||
is_group_a_cg_snapshot_type.return_value = True
|
||||
request.side_effect = [FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT_PAIR),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
@ -935,7 +944,7 @@ class HPEXPRESTISCSIDriverTest(test.TestCase):
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(5, request.call_count)
|
||||
self.assertEqual(6, request.call_count)
|
||||
actual = (
|
||||
None,
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
|
@ -187,6 +187,7 @@ GET_LDEV_RESULT = {
|
||||
"attributes": ["CVS", "DP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"label": "00000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_MAPPED = {
|
||||
@ -204,11 +205,29 @@ GET_LDEV_RESULT_MAPPED = {
|
||||
],
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_SNAP = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "DP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_PAIR = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "DP", "SS"],
|
||||
"status": "NML",
|
||||
"label": "00000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_PAIR_SNAP = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "DP", "SS"],
|
||||
"status": "NML",
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_SNAPSHOTS_RESULT = {
|
||||
@ -691,17 +710,18 @@ class VStorageRESTFCDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common._stats = {}
|
||||
self.driver.common._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
ret = self.driver.create_snapshot(TEST_SNAPSHOT[0])
|
||||
self.assertEqual('1', ret['provider_location'])
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR_SNAP),
|
||||
FakeResponse(200, NOTFOUND_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
@ -717,7 +737,7 @@ class VStorageRESTFCDriverTest(test.TestCase):
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_snapshot_no_pair(self, request):
|
||||
"""Normal case: Delete a snapshot without pair."""
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_SNAP),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
@ -938,17 +958,12 @@ class VStorageRESTFCDriverTest(test.TestCase):
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_update_migrated_volume(self, request):
|
||||
request.return_value = FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.driver.update_migrated_volume,
|
||||
self.ctxt,
|
||||
TEST_VOLUME[0],
|
||||
TEST_VOLUME[1],
|
||||
"available")
|
||||
self.assertEqual(2, request.call_count)
|
||||
args, kwargs = request.call_args_list[1]
|
||||
self.assertEqual(kwargs['json']['label'],
|
||||
TEST_VOLUME[0]['id'].replace("-", ""))
|
||||
ret = self.driver.update_migrated_volume(
|
||||
self.ctxt, TEST_VOLUME[0], TEST_VOLUME[1], "available")
|
||||
self.assertEqual(1, request.call_count)
|
||||
actual = ({'_name_id': TEST_VOLUME[1]['id'],
|
||||
'provider_location': TEST_VOLUME[1]['provider_location']})
|
||||
self.assertEqual(actual, ret)
|
||||
|
||||
def test_unmanage_snapshot(self):
|
||||
"""The driver don't support unmange_snapshot."""
|
||||
@ -1085,14 +1100,15 @@ class VStorageRESTFCDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common._stats = {}
|
||||
self.driver.common._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
{'status': 'available'},
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
@ -1108,6 +1124,7 @@ class VStorageRESTFCDriverTest(test.TestCase):
|
||||
self, is_group_a_cg_snapshot_type, volume_get, request):
|
||||
is_group_a_cg_snapshot_type.return_value = True
|
||||
request.side_effect = [FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT_PAIR),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
@ -1118,7 +1135,7 @@ class VStorageRESTFCDriverTest(test.TestCase):
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(5, request.call_count)
|
||||
self.assertEqual(6, request.call_count)
|
||||
actual = (
|
||||
None,
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
@ -1129,7 +1146,7 @@ class VStorageRESTFCDriverTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_group_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR_SNAP),
|
||||
FakeResponse(200, NOTFOUND_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
|
@ -191,6 +191,7 @@ GET_LDEV_RESULT = {
|
||||
"attributes": ["CVS", "DP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"label": "00000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_MAPPED = {
|
||||
@ -208,11 +209,29 @@ GET_LDEV_RESULT_MAPPED = {
|
||||
],
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_SNAP = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "DP"],
|
||||
"status": "NML",
|
||||
"poolId": 30,
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_PAIR = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "DP", "SS"],
|
||||
"status": "NML",
|
||||
"label": "00000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_LDEV_RESULT_PAIR_SNAP = {
|
||||
"emulationType": "OPEN-V-CVS",
|
||||
"blockCapacity": 2097152,
|
||||
"attributes": ["CVS", "DP", "SS"],
|
||||
"status": "NML",
|
||||
"label": "10000000000000000000000000000000",
|
||||
}
|
||||
|
||||
GET_POOLS_RESULT = {
|
||||
@ -584,17 +603,18 @@ class VStorageRESTISCSIDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common._stats = {}
|
||||
self.driver.common._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
ret = self.driver.create_snapshot(TEST_SNAPSHOT[0])
|
||||
self.assertEqual('1', ret['provider_location'])
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_SNAP),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
@ -797,17 +817,12 @@ class VStorageRESTISCSIDriverTest(test.TestCase):
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_update_migrated_volume(self, request):
|
||||
request.return_value = FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.driver.update_migrated_volume,
|
||||
self.ctxt,
|
||||
TEST_VOLUME[0],
|
||||
TEST_VOLUME[1],
|
||||
"available")
|
||||
self.assertEqual(2, request.call_count)
|
||||
args, kwargs = request.call_args_list[1]
|
||||
self.assertEqual(kwargs['json']['label'],
|
||||
TEST_VOLUME[0]['id'].replace("-", ""))
|
||||
ret = self.driver.update_migrated_volume(
|
||||
self.ctxt, TEST_VOLUME[0], TEST_VOLUME[1], "available")
|
||||
self.assertEqual(1, request.call_count)
|
||||
actual = ({'_name_id': TEST_VOLUME[1]['id'],
|
||||
'provider_location': TEST_VOLUME[1]['provider_location']})
|
||||
self.assertEqual(actual, ret)
|
||||
|
||||
def test_unmanage_snapshot(self):
|
||||
"""The driver don't support unmange_snapshot."""
|
||||
@ -938,14 +953,15 @@ class VStorageRESTISCSIDriverTest(test.TestCase):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
self.driver.common._stats = {}
|
||||
self.driver.common._stats['pools'] = [
|
||||
{'location_info': {'pool_id': 30}}]
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(4, request.call_count)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
{'status': 'available'},
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
@ -961,6 +977,7 @@ class VStorageRESTISCSIDriverTest(test.TestCase):
|
||||
self, is_group_a_cg_snapshot_type, volume_get, request):
|
||||
is_group_a_cg_snapshot_type.return_value = True
|
||||
request.side_effect = [FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT_PAIR),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
@ -971,7 +988,7 @@ class VStorageRESTISCSIDriverTest(test.TestCase):
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(5, request.call_count)
|
||||
self.assertEqual(6, request.call_count)
|
||||
actual = (
|
||||
None,
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
@ -982,7 +999,7 @@ class VStorageRESTISCSIDriverTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_group_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR),
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR_SNAP),
|
||||
FakeResponse(200, NOTFOUND_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
|
@ -14,6 +14,7 @@
|
||||
#
|
||||
"""Common module for Hitachi HBSD Driver."""
|
||||
|
||||
from collections import defaultdict
|
||||
import json
|
||||
import re
|
||||
|
||||
@ -47,6 +48,8 @@ _GROUP_NAME_VAR_LEN = {GROUP_NAME_VAR_WWN: _GROUP_NAME_VAR_WWN_LEN,
|
||||
STR_VOLUME = 'volume'
|
||||
STR_SNAPSHOT = 'snapshot'
|
||||
|
||||
_UUID_PATTERN = re.compile(r'^[\da-f]{32}$')
|
||||
|
||||
_INHERITED_VOLUME_OPTS = [
|
||||
'volume_backend_name',
|
||||
'volume_driver',
|
||||
@ -346,13 +349,19 @@ class HBSDCommon():
|
||||
"""Disconnect all volume pairs to which the specified S-VOL belongs."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_pair_info(self, ldev):
|
||||
def get_pair_info(self, ldev, ldev_info=None):
|
||||
"""Return volume pair info(LDEV number, pair status and pair type)."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete_pair(self, ldev):
|
||||
"""Disconnect all volume pairs to which the specified LDEV belongs."""
|
||||
pair_info = self.get_pair_info(ldev)
|
||||
def delete_pair(self, ldev, ldev_info=None):
|
||||
"""Disconnect all volume pairs to which the specified LDEV belongs.
|
||||
|
||||
:param int ldev: The ID of the LDEV whose TI pair needs be deleted
|
||||
:param dict ldev_info: LDEV info
|
||||
:return: None
|
||||
:raises VolumeDriverException: if the LDEV is a P-VOL of a TI pair
|
||||
"""
|
||||
pair_info = self.get_pair_info(ldev, ldev_info)
|
||||
if not pair_info:
|
||||
return
|
||||
if pair_info['pvol'] == ldev:
|
||||
@ -383,12 +392,57 @@ class HBSDCommon():
|
||||
"""Delete the specified LDEV from the storage."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete_ldev(self, ldev):
|
||||
"""Delete the specified LDEV."""
|
||||
self.delete_pair(ldev)
|
||||
def delete_ldev(self, ldev, ldev_info=None):
|
||||
"""Delete the specified LDEV.
|
||||
|
||||
:param int ldev: The ID of the LDEV to be deleted
|
||||
:param dict ldev_info: LDEV info
|
||||
:return: None
|
||||
"""
|
||||
self.delete_pair(ldev, ldev_info)
|
||||
self.unmap_ldev_from_storage(ldev)
|
||||
self.delete_ldev_from_storage(ldev)
|
||||
|
||||
def is_invalid_ldev(self, ldev, obj, ldev_info_):
|
||||
"""Check if the specified LDEV corresponds to the specified object.
|
||||
|
||||
If the LDEV label and the object's id or name_id do not match, the LDEV
|
||||
was deleted and another LDEV with the same ID was created for another
|
||||
volume or snapshot. In this case, we say that the LDEV is invalid.
|
||||
If the LDEV label is not set or its format is unexpected, we cannot
|
||||
judge if the LDEV corresponds to the object. This can happen if the
|
||||
LDEV was created in older versions of this product or if the user
|
||||
overwrote the label. In this case, we just say that the LDEV is not
|
||||
invalid, although we are not completely sure about it.
|
||||
The reason for using name_id rather than id for volumes in comparison
|
||||
is that id of the volume that corresponds to the LDEV changes by
|
||||
host-assisted migration while that is not the case with name_id and
|
||||
that the LDEV label is created from id of the volume when the LDEV is
|
||||
created and is never changed after that.
|
||||
Because Snapshot objects do not have name_id, we use id instead of
|
||||
name_id if the object is a Snapshot. We assume that the object is a
|
||||
Snapshot object if hasattr(obj, 'name_id') returns False.
|
||||
This method returns False if the LDEV does not exist on the storage.
|
||||
The absence of the LDEV on the storage is detected elsewhere.
|
||||
:param int ldev: The ID of the LDEV to be checked
|
||||
:param obj: The object to be checked
|
||||
:type obj: Volume or Snapshot
|
||||
:param dict ldev_info_: LDEV info. This is an output area. Data is
|
||||
written by this method, but the area must be secured by the caller.
|
||||
:return: True if the LDEV does not correspond to the object, False
|
||||
otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
ldev_info = self.get_ldev_info(None, ldev)
|
||||
# To avoid calling the same REST API multiple times, we pass the LDEV
|
||||
# info to the caller.
|
||||
ldev_info_.update(ldev_info)
|
||||
return ('label' in ldev_info
|
||||
and _UUID_PATTERN.match(ldev_info['label'])
|
||||
and ldev_info['label'] != (
|
||||
obj.name_id if hasattr(obj, 'name_id') else
|
||||
obj.id).replace('-', ''))
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Delete the specified volume."""
|
||||
ldev = self.get_ldev(volume)
|
||||
@ -397,8 +451,18 @@ class HBSDCommon():
|
||||
MSG.INVALID_LDEV_FOR_DELETION,
|
||||
method='delete_volume', id=volume['id'])
|
||||
return
|
||||
# Check if the LDEV corresponds to the volume.
|
||||
# To avoid KeyError when accessing a missing attribute, set the default
|
||||
# value to None.
|
||||
ldev_info = defaultdict(lambda: None)
|
||||
if self.is_invalid_ldev(ldev, volume, ldev_info):
|
||||
# If the LDEV is assigned to another object, skip deleting it.
|
||||
self.output_log(MSG.SKIP_DELETING_LDEV, obj='volume',
|
||||
obj_id=volume.id, ldev=ldev,
|
||||
ldev_label=ldev_info['label'])
|
||||
return
|
||||
try:
|
||||
self.delete_ldev(ldev)
|
||||
self.delete_ldev(ldev, ldev_info)
|
||||
except exception.VolumeDriverException as ex:
|
||||
if utils.BUSY_MESSAGE in ex.msg:
|
||||
raise exception.VolumeIsBusy(volume_name=volume['name'])
|
||||
@ -422,6 +486,7 @@ class HBSDCommon():
|
||||
new_ldev = self.copy_on_storage(
|
||||
ldev, size, extra_specs, pool_id, snap_pool_id, ldev_range,
|
||||
is_snapshot=True)
|
||||
self.modify_ldev_name(new_ldev, snapshot.id.replace("-", ""))
|
||||
return {
|
||||
'provider_location': str(new_ldev),
|
||||
}
|
||||
@ -434,8 +499,18 @@ class HBSDCommon():
|
||||
MSG.INVALID_LDEV_FOR_DELETION, method='delete_snapshot',
|
||||
id=snapshot['id'])
|
||||
return
|
||||
# Check if the LDEV corresponds to the snapshot.
|
||||
# To avoid KeyError when accessing a missing attribute, set the default
|
||||
# value to None.
|
||||
ldev_info = defaultdict(lambda: None)
|
||||
if self.is_invalid_ldev(ldev, snapshot, ldev_info):
|
||||
# If the LDEV is assigned to another object, skip deleting it.
|
||||
self.output_log(MSG.SKIP_DELETING_LDEV, obj='snapshot',
|
||||
obj_id=snapshot.id, ldev=ldev,
|
||||
ldev_label=ldev_info['label'])
|
||||
return
|
||||
try:
|
||||
self.delete_ldev(ldev)
|
||||
self.delete_ldev(ldev, ldev_info)
|
||||
except exception.VolumeDriverException as ex:
|
||||
if utils.BUSY_MESSAGE in ex.msg:
|
||||
raise exception.SnapshotIsBusy(snapshot_name=snapshot['name'])
|
||||
@ -1102,12 +1177,10 @@ class HBSDCommon():
|
||||
"""Migrate the specified volume."""
|
||||
return False
|
||||
|
||||
def update_migrated_volume(self, volume, new_volume):
|
||||
"""Update LDEV settings after generic volume migration."""
|
||||
ldev = self.get_ldev(new_volume)
|
||||
# We do not need to check if ldev is not None because it is guaranteed
|
||||
# that ldev is not None because migration has been successful so far.
|
||||
self.modify_ldev_name(ldev, volume['id'].replace("-", ""))
|
||||
def update_migrated_volume(self, new_volume):
|
||||
"""Return model update for migrated volume."""
|
||||
return {'_name_id': new_volume.name_id,
|
||||
'provider_location': new_volume.provider_location}
|
||||
|
||||
def retype(self, ctxt, volume, new_type, diff, host):
|
||||
"""Retype the specified volume."""
|
||||
@ -1164,7 +1237,11 @@ class HBSDCommon():
|
||||
provider_location = obj.get('provider_location')
|
||||
if not provider_location:
|
||||
return None
|
||||
if provider_location.isdigit():
|
||||
if provider_location.isdigit() and not getattr(self, 'is_secondary',
|
||||
False):
|
||||
# This format implies that the value is the ID of an LDEV in the
|
||||
# primary storage. Therefore, the secondary instance should not
|
||||
# retrieve this value.
|
||||
return int(provider_location)
|
||||
if provider_location.startswith('{'):
|
||||
loc = json.loads(provider_location)
|
||||
|
@ -191,9 +191,7 @@ class HBSDFCDriver(driver.FibreChannelDriver):
|
||||
self, ctxt, volume, new_volume, original_volume_status):
|
||||
"""Do any remaining jobs after migration."""
|
||||
self.common.discard_zero_page(new_volume)
|
||||
self.common.update_migrated_volume(volume, new_volume)
|
||||
super(HBSDFCDriver, self).update_migrated_volume(
|
||||
ctxt, volume, new_volume, original_volume_status)
|
||||
return self.common.update_migrated_volume(new_volume)
|
||||
|
||||
@volume_utils.trace
|
||||
def copy_image_to_volume(self, context, volume, image_service, image_id,
|
||||
|
@ -187,9 +187,7 @@ class HBSDISCSIDriver(driver.ISCSIDriver):
|
||||
self, ctxt, volume, new_volume, original_volume_status):
|
||||
"""Do any remaining jobs after migration."""
|
||||
self.common.discard_zero_page(new_volume)
|
||||
self.common.update_migrated_volume(volume, new_volume)
|
||||
super(HBSDISCSIDriver, self).update_migrated_volume(
|
||||
ctxt, volume, new_volume, original_volume_status)
|
||||
return self.common.update_migrated_volume(new_volume)
|
||||
|
||||
@volume_utils.trace
|
||||
def copy_image_to_volume(self, context, volume, image_service, image_id,
|
||||
|
@ -14,6 +14,7 @@
|
||||
#
|
||||
"""replication module for Hitachi HBSD Driver."""
|
||||
|
||||
from collections import defaultdict
|
||||
import json
|
||||
|
||||
from eventlet import greenthread
|
||||
@ -562,16 +563,32 @@ class HBSDREPLICATION(rest.HBSDREST):
|
||||
}
|
||||
return self.rep_primary.create_volume(volume)
|
||||
|
||||
def _has_rep_pair(self, ldev):
|
||||
ldev_info = self.rep_primary.get_ldev_info(
|
||||
['status', 'attributes'], ldev)
|
||||
def _has_rep_pair(self, ldev, ldev_info=None):
|
||||
"""Return if the specified LDEV has a replication pair.
|
||||
|
||||
:param int ldev: The LDEV ID
|
||||
:param dict ldev_info: LDEV info
|
||||
:return: True if the LDEV status is normal and the LDEV has a
|
||||
replication pair, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
if ldev_info is None:
|
||||
ldev_info = self.rep_primary.get_ldev_info(
|
||||
['status', 'attributes'], ldev)
|
||||
return (ldev_info['status'] == rest.NORMAL_STS and
|
||||
self.driver_info['mirror_attr'] in ldev_info['attributes'])
|
||||
|
||||
def _get_rep_pair_info(self, pldev):
|
||||
"""Return replication pair info."""
|
||||
def _get_rep_pair_info(self, pldev, ldev_info=None):
|
||||
"""Return replication pair info.
|
||||
|
||||
:param int pldev: The ID of the LDEV(P-VOL in case of a pair)
|
||||
:param dict ldev_info: LDEV info
|
||||
:return: replication pair info. An empty dict if the LDEV does not
|
||||
have a pair.
|
||||
:rtype: dict
|
||||
"""
|
||||
pair_info = {}
|
||||
if not self._has_rep_pair(pldev):
|
||||
if not self._has_rep_pair(pldev, ldev_info):
|
||||
return pair_info
|
||||
self._require_rep_secondary()
|
||||
copy_group_name = self._create_rep_copy_group_name(pldev)
|
||||
@ -609,6 +626,65 @@ class HBSDREPLICATION(rest.HBSDREST):
|
||||
self.rep_primary.client.delete_remote_copypair(
|
||||
self.rep_secondary.client, copy_group_name, pvol, svol)
|
||||
|
||||
def _delete_volume_pre_check(self, volume):
|
||||
"""Pre-check for delete_volume().
|
||||
|
||||
:param Volume volume: The volume to be checked
|
||||
:return: svol: The ID of the S-VOL
|
||||
:rtype: int
|
||||
:return: pvol_is_invalid: True if P-VOL is invalid, False otherwise
|
||||
:rtype: bool
|
||||
:return: svol_is_invalid: True if S-VOL is invalid, False otherwise
|
||||
:rtype: bool
|
||||
:return: pair_exists: True if the pair exists, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
# Check if the LDEV in the primary storage corresponds to the volume
|
||||
pvol_is_invalid = True
|
||||
# To avoid KeyError when accessing a missing attribute, set the default
|
||||
# value to None.
|
||||
pvol_info = defaultdict(lambda: None)
|
||||
pvol = self.rep_primary.get_ldev(volume)
|
||||
if pvol is not None:
|
||||
if self.rep_primary.is_invalid_ldev(pvol, volume, pvol_info):
|
||||
# If the LDEV is assigned to another object, skip deleting it.
|
||||
self.rep_primary.output_log(
|
||||
MSG.SKIP_DELETING_LDEV, obj='volume', obj_id=volume.id,
|
||||
ldev=pvol, ldev_label=pvol_info['label'])
|
||||
else:
|
||||
pvol_is_invalid = False
|
||||
# Check if the pair exists on the storage.
|
||||
pair_exists = False
|
||||
svol_is_invalid = True
|
||||
svol = None
|
||||
if not pvol_is_invalid:
|
||||
pair_info = self._get_rep_pair_info(pvol, pvol_info)
|
||||
if pair_info:
|
||||
pair_exists = True
|
||||
# Because this pair is a valid P-VOL's pair, we need to delete
|
||||
# it and its LDEVs. The LDEV ID of the S-VOL to be deleted is
|
||||
# uniquely determined from the pair info. Therefore, there is
|
||||
# no need to get it from provider_location or to validate the
|
||||
# S-VOL by comparing the volume ID with the S-VOL's label.
|
||||
svol = pair_info['svol_info'][0]['ldev']
|
||||
svol_is_invalid = False
|
||||
# Check if the LDEV in the secondary storage corresponds to the volume
|
||||
if svol_is_invalid:
|
||||
svol = self.rep_secondary.get_ldev(volume)
|
||||
if svol is not None:
|
||||
# To avoid KeyError when accessing a missing attribute, set the
|
||||
# default value to None.
|
||||
svol_info = defaultdict(lambda: None)
|
||||
if self.rep_secondary.is_invalid_ldev(svol, volume, svol_info):
|
||||
# If the LDEV is assigned to another object, skip deleting
|
||||
# it.
|
||||
self.rep_secondary.output_log(
|
||||
MSG.SKIP_DELETING_LDEV, obj='volume', obj_id=volume.id,
|
||||
ldev=svol, ldev_label=svol_info['label'])
|
||||
else:
|
||||
svol_is_invalid = False
|
||||
return svol, pvol_is_invalid, svol_is_invalid, pair_exists
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Delete the specified volume."""
|
||||
self._require_rep_primary()
|
||||
@ -618,22 +694,34 @@ class HBSDREPLICATION(rest.HBSDREST):
|
||||
MSG.INVALID_LDEV_FOR_DELETION, method='delete_volume',
|
||||
id=volume.id)
|
||||
return
|
||||
pair_info = self._get_rep_pair_info(ldev)
|
||||
if pair_info:
|
||||
self._delete_rep_pair(
|
||||
pair_info['pvol'], pair_info['svol_info'][0]['ldev'])
|
||||
# Run pre-check.
|
||||
svol, pvol_is_invalid, svol_is_invalid, pair_exists = (
|
||||
self._delete_volume_pre_check(volume))
|
||||
# Delete the pair if it exists.
|
||||
if pair_exists:
|
||||
self._delete_rep_pair(ldev, svol)
|
||||
# Delete LDEVs if they are valid.
|
||||
thread = None
|
||||
if not svol_is_invalid:
|
||||
thread = greenthread.spawn(
|
||||
self.rep_secondary.delete_volume, volume)
|
||||
try:
|
||||
try:
|
||||
if not pvol_is_invalid:
|
||||
self.rep_primary.delete_volume(volume)
|
||||
finally:
|
||||
finally:
|
||||
if thread is not None:
|
||||
thread.wait()
|
||||
else:
|
||||
self.rep_primary.delete_volume(volume)
|
||||
|
||||
def delete_ldev(self, ldev):
|
||||
def delete_ldev(self, ldev, ldev_info=None):
|
||||
"""Delete the specified LDEV[s].
|
||||
|
||||
:param int ldev: The ID of the LDEV(P-VOL in case of a pair) to be
|
||||
deleted
|
||||
:param dict ldev_info: LDEV(P-VOL in case of a pair) info
|
||||
:return: None
|
||||
"""
|
||||
self._require_rep_primary()
|
||||
pair_info = self._get_rep_pair_info(ldev)
|
||||
pair_info = self._get_rep_pair_info(ldev, ldev_info)
|
||||
if pair_info:
|
||||
self._delete_rep_pair(ldev, pair_info['svol_info'][0]['ldev'])
|
||||
th = greenthread.spawn(self.rep_secondary.delete_ldev,
|
||||
@ -940,23 +1028,6 @@ class HBSDREPLICATION(rest.HBSDREST):
|
||||
else:
|
||||
return self.rep_primary.migrate_volume(volume, host)
|
||||
|
||||
def update_migrated_volume(self, volume, new_volume):
|
||||
"""Update LDEV settings after generic volume migration."""
|
||||
self._require_rep_primary()
|
||||
ldev = self.rep_primary.get_ldev(new_volume)
|
||||
# We do not need to check if ldev is not None because it is guaranteed
|
||||
# that ldev is not None because migration has been successful so far.
|
||||
if self._has_rep_pair(ldev):
|
||||
self._require_rep_secondary()
|
||||
thread = greenthread.spawn(
|
||||
self.rep_secondary.update_migrated_volume, volume, new_volume)
|
||||
try:
|
||||
self.rep_primary.update_migrated_volume(volume, new_volume)
|
||||
finally:
|
||||
thread.wait()
|
||||
else:
|
||||
self.rep_primary.update_migrated_volume(volume, new_volume)
|
||||
|
||||
def _resync_rep_pair(self, pvol, svol):
|
||||
copy_group_name = self._create_rep_copy_group_name(pvol)
|
||||
rep_type = self.driver_info['mirror_attr']
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2020, 2023, Hitachi, Ltd.
|
||||
# Copyright (C) 2020, 2024, Hitachi, Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
@ -522,9 +522,22 @@ class HBSDREST(common.HBSDCommon):
|
||||
self._create_clone_pair(pvol, svol, snap_pool_id)
|
||||
|
||||
def get_ldev_info(self, keys, ldev, **kwargs):
|
||||
"""Return a dictionary of LDEV-related items."""
|
||||
"""Return a dictionary of LDEV-related items.
|
||||
|
||||
:param keys: LDEV Attributes to be obtained. Specify None to obtain all
|
||||
LDEV attributes.
|
||||
:type keys: list or NoneType
|
||||
:param int ldev: The LDEV ID
|
||||
:param dict kwargs: REST API options
|
||||
:return: LDEV info
|
||||
:rtype: dict
|
||||
"""
|
||||
d = {}
|
||||
result = self.client.get_ldev(ldev, **kwargs)
|
||||
if not keys:
|
||||
# To avoid KeyError when accessing a missing attribute, set the
|
||||
# default value to None.
|
||||
return defaultdict(lambda: None, result)
|
||||
for key in keys:
|
||||
d[key] = result.get(key)
|
||||
return d
|
||||
@ -909,10 +922,17 @@ class HBSDREST(common.HBSDCommon):
|
||||
|
||||
return pvol, [{'ldev': svol, 'is_psus': is_psus, 'status': status}]
|
||||
|
||||
def get_pair_info(self, ldev):
|
||||
"""Return info of the volume pair."""
|
||||
def get_pair_info(self, ldev, ldev_info=None):
|
||||
"""Return info of the volume pair.
|
||||
|
||||
:param int ldev: The LDEV ID
|
||||
:param dict ldev_info: LDEV info
|
||||
:return: TI pair info if the LDEV has TI pairs, None otherwise
|
||||
:rtype: dict or NoneType
|
||||
"""
|
||||
pair_info = {}
|
||||
ldev_info = self.get_ldev_info(['status', 'attributes'], ldev)
|
||||
if ldev_info is None:
|
||||
ldev_info = self.get_ldev_info(['status', 'attributes'], ldev)
|
||||
if (ldev_info['status'] != NORMAL_STS or
|
||||
self.driver_info['pair_attr'] not in ldev_info['attributes']):
|
||||
return None
|
||||
@ -1278,6 +1298,8 @@ class HBSDREST(common.HBSDCommon):
|
||||
extra_specs = self.get_volume_extra_specs(snapshot.volume)
|
||||
pair['svol'] = self.create_ldev(size, extra_specs,
|
||||
pool_id, ldev_range)
|
||||
self.modify_ldev_name(pair['svol'],
|
||||
snapshot.id.replace("-", ""))
|
||||
except Exception as exc:
|
||||
pair['msg'] = utils.get_exception_msg(exc)
|
||||
raise loopingcall.LoopingCallDone(pair)
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2020, 2023, Hitachi, Ltd.
|
||||
# Copyright (C) 2020, 2024, Hitachi, Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
@ -253,6 +253,14 @@ class HBSDMsg(enum.Enum):
|
||||
'to be active by the Fibre Channel Zone Manager.',
|
||||
'suffix': WARNING_SUFFIX,
|
||||
}
|
||||
SKIP_DELETING_LDEV = {
|
||||
'msg_id': 348,
|
||||
'loglevel': base_logging.WARNING,
|
||||
'msg': 'Skip deleting the LDEV and its LUNs and pairs because the '
|
||||
'LDEV is used by another object. (%(obj)s: %(obj_id)s, LDEV: '
|
||||
'%(ldev)s, LDEV label: %(ldev_label)s)',
|
||||
'suffix': WARNING_SUFFIX,
|
||||
}
|
||||
STORAGE_COMMAND_FAILED = {
|
||||
'msg_id': 600,
|
||||
'loglevel': base_logging.ERROR,
|
||||
|
@ -143,6 +143,12 @@ If you use iSCSI:
|
||||
1. ``Ports``
|
||||
Assign an IP address and a TCP port number to the port.
|
||||
|
||||
.. note::
|
||||
|
||||
* Do not change LDEV nickname for the LDEVs created by Hitachi block
|
||||
storage driver. The nickname is referred when deleting a volume or
|
||||
a snapshot, to avoid data-loss risk. See details in `bug #2072317`_.
|
||||
|
||||
Set up Hitachi storage volume driver and volume operations
|
||||
----------------------------------------------------------
|
||||
|
||||
@ -508,3 +514,5 @@ attach operations for each volume type.
|
||||
https://docs.hitachivantara.com/r/en-us/svos/9.8.7/mk-97hm85026/
|
||||
about-adaptive-data-reduction/capacity-saving/
|
||||
capacity-saving-function-data-deduplication-and-compression
|
||||
.. _bug #2072317:
|
||||
https://bugs.launchpad.net/cinder/+bug/2072317
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
Hitachi driver `bug #2072317
|
||||
<https://bugs.launchpad.net/cinder/+bug/2072317>`_: Fix potential
|
||||
data-loss due to a network issue during a volume deletion.
|
Loading…
Reference in New Issue
Block a user