summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-06-14 08:04:15 +0000
committerGerrit Code Review <review@openstack.org>2018-06-14 08:04:16 +0000
commit4335b3e5442eb7cf91bb95b3aacdcc55f07b1f8b (patch)
tree6f2de354a643fca2c3b088ef871442832cf607aa
parent22a5a23e7c1bc94ae4f2cd98db120c399cd2207a (diff)
parentd05a7a10dc04335c205ab3ee5a2d03a62c26b8e8 (diff)
Merge "VMAX driver - VMAX list manageable volumes and snapshots."
-rw-r--r--cinder/tests/unit/volume/drivers/dell_emc/vmax/test_vmax.py414
-rw-r--r--cinder/volume/drivers/dell_emc/vmax/common.py197
-rw-r--r--cinder/volume/drivers/dell_emc/vmax/fc.py36
-rw-r--r--cinder/volume/drivers/dell_emc/vmax/iscsi.py36
-rw-r--r--cinder/volume/drivers/dell_emc/vmax/rest.py66
-rw-r--r--cinder/volume/drivers/dell_emc/vmax/utils.py78
-rw-r--r--releasenotes/notes/vmax-list-manageable-vols-snaps-6a7f5aa114fae8f3.yaml4
7 files changed, 831 insertions, 0 deletions
diff --git a/cinder/tests/unit/volume/drivers/dell_emc/vmax/test_vmax.py b/cinder/tests/unit/volume/drivers/dell_emc/vmax/test_vmax.py
index c22d2bd..c613e7d 100644
--- a/cinder/tests/unit/volume/drivers/dell_emc/vmax/test_vmax.py
+++ b/cinder/tests/unit/volume/drivers/dell_emc/vmax/test_vmax.py
@@ -683,6 +683,232 @@ class VMAXCommonData(object):
683 683
684 headroom = {"headroom": [{"headroomCapacity": 20348.29}]} 684 headroom = {"headroom": [{"headroomCapacity": 20348.29}]}
685 685
686 private_vol_rest_response_single = {
687 "id": "f3aab01c-a5a8-4fb4-af2b-16ae1c46dc9e_0", "count": 1,
688 "expirationTime": 1521650650793, "maxPageSize": 1000,
689 "resultList": {"to": 1, "from": 1, "result": [
690 {"volumeHeader": {
691 "capGB": 1.0, "capMB": 1026.0, "volumeId": "00001",
692 "status": "Ready", "configuration": "TDEV"}}]}}
693 private_vol_rest_response_none = {
694 "id": "f3aab01c-a5a8-4fb4-af2b-16ae1c46dc9e_0", "count": 0,
695 "expirationTime": 1521650650793, "maxPageSize": 1000,
696 "resultList": {"to": 0, "from": 0, "result": []}}
697 private_vol_rest_response_iterator_first = {
698 "id": "f3aab01c-a5a8-4fb4-af2b-16ae1c46dc9e_0", "count": 1500,
699 "expirationTime": 1521650650793, "maxPageSize": 1000,
700 "resultList": {"to": 1, "from": 1, "result": [
701 {"volumeHeader": {
702 "capGB": 1.0, "capMB": 1026.0, "volumeId": "00002",
703 "status": "Ready", "configuration": "TDEV"}}]}}
704 private_vol_rest_response_iterator_second = {
705 "to": 2000, "from": 1001, "result": [
706 {"volumeHeader": {
707 "capGB": 1.0, "capMB": 1026.0, "volumeId": "00001",
708 "status": "Ready", "configuration": "TDEV"}}]}
709 rest_iterator_resonse_one = {
710 "to": 1000, "from": 1, "result": [
711 {"volumeHeader": {
712 "capGB": 1.0, "capMB": 1026.0, "volumeId": "00001",
713 "status": "Ready", "configuration": "TDEV"}}]}
714 rest_iterator_resonse_two = {
715 "to": 1500, "from": 1001, "result": [
716 {"volumeHeader": {
717 "capGB": 1.0, "capMB": 1026.0, "volumeId": "00002",
718 "status": "Ready", "configuration": "TDEV"}}]}
719
720 # COMMON.PY
721 priv_vol_func_response_single = [
722 {"volumeHeader": {
723 "private": False, "capGB": 1.0, "capMB": 1026.0,
724 "serviceState": "Normal", "emulationType": "FBA",
725 "volumeId": "00001", "status": "Ready", "mapped": False,
726 "numStorageGroups": 0, "reservationInfo": {"reserved": False},
727 "encapsulated": False, "formattedName": "00001",
728 "system_resource": False, "numSymDevMaskingViews": 0,
729 "nameModifier": "", "configuration": "TDEV"},
730 "maskingInfo": {"masked": False},
731 "rdfInfo": {
732 "dynamicRDF": False, "RDF": False,
733 "concurrentRDF": False,
734 "getDynamicRDFCapability": "RDF1_Capable", "RDFA": False},
735 "timeFinderInfo": {
736 "mirror": False, "snapVXTgt": False,
737 "cloneTarget": False, "cloneSrc": False,
738 "snapVXSrc": True, "snapVXSession": [
739 {"srcSnapshotGenInfo": [
740 {"snapshotHeader": {
741 "timestamp": 1512763278000, "expired": False,
742 "secured": False, "snapshotName": "testSnap1",
743 "device": "00001", "generation": 0, "timeToLive": 0
744 }}]}]}}]
745
746 priv_vol_func_response_multi = [
747 {"volumeHeader": {
748 "private": False, "capGB": 100.0, "capMB": 102400.0,
749 "serviceState": "Normal", "emulationType": "FBA",
750 "volumeId": "00001", "status": "Ready", "numStorageGroups": 0,
751 "reservationInfo": {"reserved": False}, "mapped": False,
752 "encapsulated": False, "formattedName": "00001",
753 "system_resource": False, "numSymDevMaskingViews": 0,
754 "nameModifier": "", "configuration": "TDEV"},
755 "rdfInfo": {
756 "dynamicRDF": False, "RDF": False,
757 "concurrentRDF": False,
758 "getDynamicRDFCapability": "RDF1_Capable", "RDFA": False},
759 "maskingInfo": {"masked": False},
760 "timeFinderInfo": {
761 "mirror": False, "snapVXTgt": False,
762 "cloneTarget": False, "cloneSrc": False,
763 "snapVXSrc": True, "snapVXSession": [
764 {"srcSnapshotGenInfo": [
765 {"snapshotHeader": {
766 "timestamp": 1512763278000, "expired": False,
767 "secured": False, "snapshotName": "testSnap1",
768 "device": "00001", "generation": 0, "timeToLive": 0
769 }}]}]}},
770 {"volumeHeader": {
771 "private": False, "capGB": 200.0, "capMB": 204800.0,
772 "serviceState": "Normal", "emulationType": "FBA",
773 "volumeId": "00002", "status": "Ready", "numStorageGroups": 0,
774 "reservationInfo": {"reserved": False}, "mapped": False,
775 "encapsulated": False, "formattedName": "00002",
776 "system_resource": False, "numSymDevMaskingViews": 0,
777 "nameModifier": "", "configuration": "TDEV"},
778 "rdfInfo": {
779 "dynamicRDF": False, "RDF": False,
780 "concurrentRDF": False,
781 "getDynamicRDFCapability": "RDF1_Capable", "RDFA": False},
782 "maskingInfo": {"masked": False},
783 "timeFinderInfo": {
784 "mirror": False, "snapVXTgt": False,
785 "cloneTarget": False, "cloneSrc": False,
786 "snapVXSrc": True, "snapVXSession": [
787 {"srcSnapshotGenInfo": [
788 {"snapshotHeader": {
789 "timestamp": 1512763278000, "expired": False,
790 "secured": False, "snapshotName": "testSnap2",
791 "device": "00002", "generation": 0, "timeToLive": 0
792 }}]}]}},
793 {"volumeHeader": {
794 "private": False, "capGB": 300.0, "capMB": 307200.0,
795 "serviceState": "Normal", "emulationType": "FBA",
796 "volumeId": "00003", "status": "Ready", "numStorageGroups": 0,
797 "reservationInfo": {"reserved": False}, "mapped": False,
798 "encapsulated": False, "formattedName": "00003",
799 "system_resource": False, "numSymDevMaskingViews": 0,
800 "nameModifier": "", "configuration": "TDEV"},
801 "rdfInfo": {
802 "dynamicRDF": False, "RDF": False,
803 "concurrentRDF": False,
804 "getDynamicRDFCapability": "RDF1_Capable", "RDFA": False},
805 "maskingInfo": {"masked": False},
806 "timeFinderInfo": {
807 "mirror": False, "snapVXTgt": False,
808 "cloneTarget": False, "cloneSrc": False,
809 "snapVXSrc": True, "snapVXSession": [
810 {"srcSnapshotGenInfo": [
811 {"snapshotHeader": {
812 "timestamp": 1512763278000, "expired": False,
813 "secured": False, "snapshotName": "testSnap3",
814 "device": "00003", "generation": 0, "timeToLive": 0
815 }}]}]}},
816 {"volumeHeader": {
817 "private": False, "capGB": 400.0, "capMB": 409600.0,
818 "serviceState": "Normal", "emulationType": "FBA",
819 "volumeId": "00004", "status": "Ready", "numStorageGroups": 0,
820 "reservationInfo": {"reserved": False}, "mapped": False,
821 "encapsulated": False, "formattedName": "00004",
822 "system_resource": False, "numSymDevMaskingViews": 0,
823 "nameModifier": "", "configuration": "TDEV"},
824 "rdfInfo": {
825 "dynamicRDF": False, "RDF": False,
826 "concurrentRDF": False,
827 "getDynamicRDFCapability": "RDF1_Capable", "RDFA": False},
828 "maskingInfo": {"masked": False},
829 "timeFinderInfo": {
830 "mirror": False, "snapVXTgt": False,
831 "cloneTarget": False, "cloneSrc": False,
832 "snapVXSrc": True, "snapVXSession": [
833 {"srcSnapshotGenInfo": [
834 {"snapshotHeader": {
835 "timestamp": 1512763278000, "expired": False,
836 "secured": False, "snapshotName": "testSnap4",
837 "device": "00004", "generation": 0, "timeToLive": 0
838 }}]}]}}]
839
840 priv_vol_func_response_multi_invalid = [
841 {"volumeHeader": {
842 "private": False, "capGB": 1.0, "capMB": 10.0,
843 "serviceState": "Normal", "emulationType": "FBA",
844 "volumeId": "00001", "status": "Ready", "mapped": False,
845 "numStorageGroups": 0, "reservationInfo": {"reserved": False},
846 "encapsulated": False, "formattedName": "00001",
847 "system_resource": False, "numSymDevMaskingViews": 0,
848 "nameModifier": "", "configuration": "TDEV"},
849 "maskingInfo": {"masked": False},
850 "rdfInfo": {
851 "dynamicRDF": False, "RDF": False,
852 "concurrentRDF": False,
853 "getDynamicRDFCapability": "RDF1_Capable", "RDFA": False},
854 "timeFinderInfo": {"snapVXTgt": False, "snapVXSrc": False}},
855 {"volumeHeader": {
856 "private": False, "capGB": 1.0, "capMB": 1026.0,
857 "serviceState": "Normal", "emulationType": "FBA",
858 "volumeId": "00002", "status": "Ready", "mapped": False,
859 "numStorageGroups": 0, "reservationInfo": {"reserved": False},
860 "encapsulated": False, "formattedName": "00002",
861 "system_resource": False, "numSymDevMaskingViews": 1,
862 "nameModifier": "", "configuration": "TDEV"},
863 "maskingInfo": {"masked": False},
864 "rdfInfo": {
865 "dynamicRDF": False, "RDF": False,
866 "concurrentRDF": False,
867 "getDynamicRDFCapability": "RDF1_Capable", "RDFA": False},
868 "timeFinderInfo": {"snapVXTgt": False, "snapVXSrc": False}},
869 {"volumeHeader": {
870 "private": False, "capGB": 1.0, "capMB": 1026.0,
871 "serviceState": "Normal", "emulationType": "CKD",
872 "volumeId": "00003", "status": "Ready", "mapped": False,
873 "numStorageGroups": 0, "reservationInfo": {"reserved": False},
874 "encapsulated": False, "formattedName": "00003",
875 "system_resource": False, "numSymDevMaskingViews": 0,
876 "nameModifier": "", "configuration": "TDEV"},
877 "maskingInfo": {"masked": False},
878 "rdfInfo": {
879 "dynamicRDF": False, "RDF": False,
880 "concurrentRDF": False,
881 "getDynamicRDFCapability": "RDF1_Capable", "RDFA": False},
882 "timeFinderInfo": {"snapVXTgt": False, "snapVXSrc": False}},
883 {"volumeHeader": {
884 "private": False, "capGB": 1.0, "capMB": 1026.0,
885 "serviceState": "Normal", "emulationType": "FBA",
886 "volumeId": "00004", "status": "Ready", "mapped": False,
887 "numStorageGroups": 0, "reservationInfo": {"reserved": False},
888 "encapsulated": False, "formattedName": "00004",
889 "system_resource": False, "numSymDevMaskingViews": 0,
890 "nameModifier": "", "configuration": "TDEV"},
891 "maskingInfo": {"masked": False},
892 "rdfInfo": {
893 "dynamicRDF": False, "RDF": False,
894 "concurrentRDF": False,
895 "getDynamicRDFCapability": "RDF1_Capable", "RDFA": False},
896 "timeFinderInfo": {"snapVXTgt": True, "snapVXSrc": False}},
897 {"volumeHeader": {
898 "private": False, "capGB": 1.0, "capMB": 1026.0,
899 "serviceState": "Normal", "emulationType": "FBA",
900 "volumeId": "00005", "status": "Ready", "mapped": False,
901 "numStorageGroups": 0, "reservationInfo": {"reserved": False},
902 "encapsulated": False, "formattedName": "00005",
903 "system_resource": False, "numSymDevMaskingViews": 0,
904 "nameModifier": "OS-vol", "configuration": "TDEV"},
905 "maskingInfo": {"masked": False},
906 "rdfInfo": {
907 "dynamicRDF": False, "RDF": False,
908 "concurrentRDF": False,
909 "getDynamicRDFCapability": "RDF1_Capable", "RDFA": False},
910 "timeFinderInfo": {"snapVXTgt": False, "snapVXSrc": False}}]
911
686 912
687class FakeLookupService(object): 913class FakeLookupService(object):
688 def get_device_mapping_from_network(self, initiator_wwns, target_wwns): 914 def get_device_mapping_from_network(self, initiator_wwns, target_wwns):
@@ -1543,6 +1769,22 @@ class VMAXUtilsTest(test.TestCase):
1543 self.assertFalse(self.utils.change_multiattach( 1769 self.assertFalse(self.utils.change_multiattach(
1544 extra_specs_ma_false, extra_specs_ma_false)) 1770 extra_specs_ma_false, extra_specs_ma_false))
1545 1771
1772 def test_is_volume_manageable(self):
1773 for volume in self.data.priv_vol_func_response_multi:
1774 self.assertTrue(
1775 self.utils.is_volume_manageable(volume))
1776 for volume in self.data.priv_vol_func_response_multi_invalid:
1777 self.assertFalse(
1778 self.utils.is_volume_manageable(volume))
1779
1780 def test_is_snapshot_manageable(self):
1781 for volume in self.data.priv_vol_func_response_multi:
1782 self.assertTrue(
1783 self.utils.is_snapshot_manageable(volume))
1784 for volume in self.data.priv_vol_func_response_multi_invalid:
1785 self.assertFalse(
1786 self.utils.is_snapshot_manageable(volume))
1787
1546 1788
1547class VMAXRestTest(test.TestCase): 1789class VMAXRestTest(test.TestCase):
1548 def setUp(self): 1790 def setUp(self):
@@ -2940,6 +3182,68 @@ class VMAXRestTest(test.TestCase):
2940 rename=True, new_snap_name=new_snap_backend_name) 3182 rename=True, new_snap_name=new_snap_backend_name)
2941 mock_modify.assert_called_once() 3183 mock_modify.assert_called_once()
2942 3184
3185 def test_get_private_volume_list_pass(self):
3186 array_id = self.data.array
3187 response = [{"volumeHeader": {
3188 "capGB": 1.0, "capMB": 1026.0, "volumeId": "00001",
3189 "status": "Ready", "configuration": "TDEV"}}]
3190
3191 with mock.patch.object(
3192 self.rest, 'get_resource',
3193 return_value=self.data.private_vol_rest_response_single):
3194 volume = self.rest.get_private_volume_list(array_id)
3195 self.assertEqual(response, volume)
3196
3197 def test_get_private_volume_list_none(self):
3198 array_id = self.data.array
3199 response = []
3200 with mock.patch.object(
3201 self.rest, 'get_resource', return_value=
3202 VMAXCommonData.private_vol_rest_response_none):
3203 vol_list = self.rest.get_private_volume_list(array_id)
3204 self.assertEqual(response, vol_list)
3205
3206 @mock.patch.object(
3207 rest.VMAXRest, 'get_iterator_page_list', return_value=
3208 VMAXCommonData.private_vol_rest_response_iterator_second['result'])
3209 @mock.patch.object(
3210 rest.VMAXRest, 'get_resource', return_value=
3211 VMAXCommonData.private_vol_rest_response_iterator_first)
3212 def test_get_private_volume_list_iterator(self, mock_get_resource,
3213 mock_iterator):
3214 array_id = self.data.array
3215 response = [
3216 {"volumeHeader": {
3217 "capGB": 1.0, "capMB": 1026.0, "volumeId": "00002",
3218 "status": "Ready", "configuration": "TDEV"}},
3219 {"volumeHeader": {
3220 "capGB": 1.0, "capMB": 1026.0, "volumeId": "00001",
3221 "status": "Ready", "configuration": "TDEV"}}]
3222 volume = self.rest.get_private_volume_list(array_id)
3223 self.assertEqual(response, volume)
3224
3225 def test_get_iterator_list(self):
3226 with mock.patch.object(
3227 self.rest, '_get_request', side_effect=[
3228 self.data.rest_iterator_resonse_one,
3229 self.data.rest_iterator_resonse_two]):
3230
3231 expected_response = [
3232 {"volumeHeader": {
3233 "capGB": 1.0, "capMB": 1026.0, "volumeId": "00001",
3234 "status": "Ready", "configuration": "TDEV"}},
3235 {"volumeHeader": {
3236 "capGB": 1.0, "capMB": 1026.0, "volumeId": "00002",
3237 "status": "Ready", "configuration": "TDEV"}}]
3238 iterator_id = 'test_iterator_id'
3239 result_count = 1500
3240 start_position = 1
3241 end_position = 1000
3242
3243 actual_response = self.rest.get_iterator_page_list(
3244 iterator_id, result_count, start_position, end_position)
3245 self.assertEqual(expected_response, actual_response)
3246
2943 3247
2944class VMAXProvisionTest(test.TestCase): 3248class VMAXProvisionTest(test.TestCase):
2945 def setUp(self): 3249 def setUp(self):
@@ -5179,6 +5483,116 @@ class VMAXCommonTest(test.TestCase):
5179 initiator_check = self.common._get_initiator_check_flag() 5483 initiator_check = self.common._get_initiator_check_flag()
5180 self.assertTrue(initiator_check) 5484 self.assertTrue(initiator_check)
5181 5485
5486 def test_get_manageable_volumes_success(self):
5487 marker = limit = offset = sort_keys = sort_dirs = None
5488 with mock.patch.object(
5489 self.rest, 'get_private_volume_list',
5490 return_value=self.data.priv_vol_func_response_single):
5491 vols_lists = self.common.get_manageable_volumes(
5492 marker, limit, offset, sort_keys, sort_dirs)
5493 expected_response = [
5494 {'reference': {'source-id': '00001'}, 'safe_to_manage': True,
5495 'size': 1.0, 'reason_not_safe': None, 'cinder_id': None,
5496 'extra_info': {'config': 'TDEV', 'emulation': 'FBA'}}]
5497 self.assertEqual(vols_lists, expected_response)
5498
5499 def test_get_manageable_volumes_filters_set(self):
5500 marker, limit, offset = '00002', 2, 1
5501 sort_keys, sort_dirs = 'size', 'desc'
5502 with mock.patch.object(
5503 self.rest, 'get_private_volume_list',
5504 return_value=self.data.priv_vol_func_response_multi):
5505 vols_lists = self.common.get_manageable_volumes(
5506 marker, limit, offset, sort_keys, sort_dirs)
5507 expected_response = [
5508 {'reference': {'source-id': '00003'}, 'safe_to_manage': True,
5509 'size': 300, 'reason_not_safe': None, 'cinder_id': None,
5510 'extra_info': {'config': 'TDEV', 'emulation': 'FBA'}},
5511 {'reference': {'source-id': '00004'}, 'safe_to_manage': True,
5512 'size': 400, 'reason_not_safe': None, 'cinder_id': None,
5513 'extra_info': {'config': 'TDEV', 'emulation': 'FBA'}}]
5514 self.assertEqual(vols_lists, expected_response)
5515
5516 def test_get_manageable_volumes_fail_no_vols(self):
5517 marker = limit = offset = sort_keys = sort_dirs = None
5518 with mock.patch.object(
5519 self.rest, 'get_private_volume_list',
5520 return_value=[]):
5521 expected_response = []
5522 vol_list = self.common.get_manageable_volumes(
5523 marker, limit, offset, sort_keys, sort_dirs)
5524 self.assertEqual(vol_list, expected_response)
5525
5526 def test_get_manageable_volumes_fail_no_valid_vols(self):
5527 marker = limit = offset = sort_keys = sort_dirs = None
5528 with mock.patch.object(
5529 self.rest, 'get_private_volume_list',
5530 return_value=self.data.priv_vol_func_response_multi_invalid):
5531 expected_response = []
5532 vol_list = self.common.get_manageable_volumes(
5533 marker, limit, offset, sort_keys, sort_dirs)
5534 self.assertEqual(vol_list, expected_response)
5535
5536 def test_get_manageable_snapshots_success(self):
5537 marker = limit = offset = sort_keys = sort_dirs = None
5538 with mock.patch.object(
5539 self.rest, 'get_private_volume_list',
5540 return_value=self.data.priv_vol_func_response_single):
5541 snap_list = self.common.get_manageable_snapshots(
5542 marker, limit, offset, sort_keys, sort_dirs)
5543 expected_response = [{
5544 'reference': {'source-name': 'testSnap1'},
5545 'safe_to_manage': True, 'size': 1,
5546 'reason_not_safe': None, 'cinder_id': None,
5547 'extra_info': {
5548 'generation': 0, 'secured': False, 'timeToLive': 'N/A',
5549 'timestamp': '2017/12/08, 20:01:18'},
5550 'source_reference': {'source-id': '00001'}}]
5551 self.assertEqual(snap_list, expected_response)
5552
5553 def test_get_manageable_snapshots_filters_set(self):
5554 marker, limit, offset = 'testSnap2', 2, 1
5555 sort_keys, sort_dirs = 'size', 'desc'
5556 with mock.patch.object(
5557 self.rest, 'get_private_volume_list',
5558 return_value=self.data.priv_vol_func_response_multi):
5559 vols_lists = self.common.get_manageable_snapshots(
5560 marker, limit, offset, sort_keys, sort_dirs)
5561 expected_response = [
5562 {'reference': {'source-name': 'testSnap3'},
5563 'safe_to_manage': True, 'size': 300, 'reason_not_safe': None,
5564 'cinder_id': None, 'extra_info': {
5565 'generation': 0, 'secured': False, 'timeToLive': 'N/A',
5566 'timestamp': '2017/12/08, 20:01:18'},
5567 'source_reference': {'source-id': '00003'}},
5568 {'reference': {'source-name': 'testSnap4'},
5569 'safe_to_manage': True, 'size': 400, 'reason_not_safe': None,
5570 'cinder_id': None, 'extra_info': {
5571 'generation': 0, 'secured': False, 'timeToLive': 'N/A',
5572 'timestamp': '2017/12/08, 20:01:18'},
5573 'source_reference': {'source-id': '00004'}}]
5574 self.assertEqual(vols_lists, expected_response)
5575
5576 def test_get_manageable_snapshots_fail_no_snaps(self):
5577 marker = limit = offset = sort_keys = sort_dirs = None
5578 with mock.patch.object(
5579 self.rest, 'get_private_volume_list',
5580 return_value=[]):
5581 expected_response = []
5582 vols_lists = self.common.get_manageable_snapshots(
5583 marker, limit, offset, sort_keys, sort_dirs)
5584 self.assertEqual(vols_lists, expected_response)
5585
5586 def test_get_manageable_snapshots_fail_no_valid_snaps(self):
5587 marker = limit = offset = sort_keys = sort_dirs = None
5588 with mock.patch.object(
5589 self.rest, 'get_private_volume_list',
5590 return_value=self.data.priv_vol_func_response_multi_invalid):
5591 expected_response = []
5592 vols_lists = self.common.get_manageable_snapshots(
5593 marker, limit, offset, sort_keys, sort_dirs)
5594 self.assertEqual(vols_lists, expected_response)
5595
5182 5596
5183class VMAXFCTest(test.TestCase): 5597class VMAXFCTest(test.TestCase):
5184 def setUp(self): 5598 def setUp(self):
diff --git a/cinder/volume/drivers/dell_emc/vmax/common.py b/cinder/volume/drivers/dell_emc/vmax/common.py
index 7635412..54a7d89 100644
--- a/cinder/volume/drivers/dell_emc/vmax/common.py
+++ b/cinder/volume/drivers/dell_emc/vmax/common.py
@@ -15,9 +15,11 @@
15 15
16import ast 16import ast
17from copy import deepcopy 17from copy import deepcopy
18import math
18import os.path 19import os.path
19import random 20import random
20import sys 21import sys
22import time
21 23
22from oslo_config import cfg 24from oslo_config import cfg
23from oslo_log import log as logging 25from oslo_log import log as logging
@@ -2140,6 +2142,201 @@ class VMAXCommon(object):
2140 "OpenStack but still remains on VMAX source " 2142 "OpenStack but still remains on VMAX source "
2141 "%(array_id)s", {'snap_name': snap_name, 'array_id': array}) 2143 "%(array_id)s", {'snap_name': snap_name, 'array_id': array})
2142 2144
2145 def get_manageable_volumes(self, marker, limit, offset, sort_keys,
2146 sort_dirs):
2147 """Lists all manageable volumes.
2148
2149 :param marker: Begin returning volumes that appear later in the volume
2150 list than that represented by this reference. This
2151 reference should be json like. Default=None.
2152 :param limit: Maximum number of volumes to return. Default=None.
2153 :param offset: Number of volumes to skip after marker. Default=None.
2154 :param sort_keys: Key to sort by, sort by size or reference. Valid
2155 keys: size, reference. Default=None.
2156 :param sort_dirs: Direction to sort by. Valid dirs: asd, desc.
2157 Default=None.
2158 :return: List of dicts containing all volumes valid for management
2159 """
2160 valid_vols = []
2161 manageable_vols = []
2162 array = self.pool_info['arrays_info'][0]["SerialNumber"]
2163 LOG.info("Listing manageable volumes for array %(array_id)s", {
2164 'array_id': array})
2165 volumes = self.rest.get_private_volume_list(array)
2166
2167 # No volumes returned from VMAX
2168 if not volumes:
2169 LOG.warning("There were no volumes found on the backend VMAX. "
2170 "You need to create some volumes before they can be "
2171 "managed into Cinder.")
2172 return manageable_vols
2173
2174 for device in volumes:
2175 # Determine if volume is valid for management
2176 if self.utils.is_volume_manageable(device):
2177 valid_vols.append(device['volumeHeader'])
2178
2179 # For all valid vols, extract relevant data for Cinder response
2180 for vol in valid_vols:
2181 volume_dict = {'reference': {'source-id': vol['volumeId']},
2182 'safe_to_manage': True,
2183 'size': int(math.ceil(vol['capGB'])),
2184 'reason_not_safe': None, 'cinder_id': None,
2185 'extra_info': {
2186 'config': vol['configuration'],
2187 'emulation': vol['emulationType']}}
2188 manageable_vols.append(volume_dict)
2189
2190 # If volume list is populated, perform filtering on user params
2191 if len(manageable_vols) > 0:
2192 # If sort keys selected, determine if by size or reference, and
2193 # direction of sort
2194 if sort_keys:
2195 reverse = False
2196 if sort_dirs:
2197 if 'desc' in sort_dirs[0]:
2198 reverse = True
2199 if sort_keys[0] == 'size':
2200 manageable_vols = sorted(manageable_vols,
2201 key=lambda k: k['size'],
2202 reverse=reverse)
2203 if sort_keys[0] == 'reference':
2204 manageable_vols = sorted(manageable_vols,
2205 key=lambda k: k['reference'][
2206 'source-id'],
2207 reverse=reverse)
2208
2209 # If marker provided, return only manageable volumes after marker
2210 if marker:
2211 vol_index = None
2212 for vol in manageable_vols:
2213 if vol['reference']['source-id'] == marker:
2214 vol_index = manageable_vols.index(vol)
2215 if vol_index:
2216 manageable_vols = manageable_vols[vol_index:]
2217 else:
2218 msg = _("Volume marker not found, please check supplied "
2219 "device ID and try again.")
2220 raise exception.VolumeBackendAPIException(msg)
2221
2222 # If offset or limit provided, offset or limit result list
2223 if offset:
2224 manageable_vols = manageable_vols[offset:]
2225 if limit:
2226 manageable_vols = manageable_vols[:limit]
2227
2228 return manageable_vols
2229
2230 def get_manageable_snapshots(self, marker, limit, offset, sort_keys,
2231 sort_dirs):
2232 """Lists all manageable snapshots.
2233
2234 :param marker: Begin returning volumes that appear later in the volume
2235 list than that represented by this reference. This
2236 reference should be json like. Default=None.
2237 :param limit: Maximum number of volumes to return. Default=None.
2238 :param offset: Number of volumes to skip after marker. Default=None.
2239 :param sort_keys: Key to sort by, sort by size or reference.
2240 Valid keys: size, reference. Default=None.
2241 :param sort_dirs: Direction to sort by. Valid dirs: asd, desc.
2242 Default=None.
2243 :return: List of dicts containing all volumes valid for management
2244 """
2245 manageable_snaps = []
2246 array = self.pool_info['arrays_info'][0]["SerialNumber"]
2247 LOG.info("Listing manageable snapshots for array %(array_id)s", {
2248 'array_id': array})
2249 volumes = self.rest.get_private_volume_list(array)
2250
2251 # No volumes returned from VMAX
2252 if not volumes:
2253 LOG.warning("There were no volumes found on the backend VMAX. "
2254 "You need to create some volumes before snapshots can "
2255 "be created and managed into Cinder.")
2256 return manageable_snaps
2257
2258 for device in volumes:
2259 # Determine if volume is valid for management
2260 if self.utils.is_snapshot_manageable(device):
2261 # Snapshot valid, extract relevant snap info
2262 snap_info = device['timeFinderInfo']['snapVXSession'][0][
2263 'srcSnapshotGenInfo'][0]['snapshotHeader']
2264 # Convert timestamp to human readable format
2265 human_timestamp = time.strftime(
2266 "%Y/%m/%d, %H:%M:%S", time.localtime(
2267 float(six.text_type(
2268 snap_info['timestamp'])[:-3])))
2269 # If TTL is set, convert value to human readable format
2270 if int(snap_info['timeToLive']) > 0:
2271 human_ttl_timestamp = time.strftime(
2272 "%Y/%m/%d, %H:%M:%S", time.localtime(
2273 float(six.text_type(
2274 snap_info['timeToLive']))))
2275 else:
2276 human_ttl_timestamp = 'N/A'
2277
2278 # For all valid snaps, extract relevant data for Cinder
2279 # response
2280 snap_dict = {
2281 'reference': {
2282 'source-name': snap_info['snapshotName']},
2283 'safe_to_manage': True,
2284 'size': int(
2285 math.ceil(device['volumeHeader']['capGB'])),
2286 'reason_not_safe': None, 'cinder_id': None,
2287 'extra_info': {
2288 'generation': snap_info['generation'],
2289 'secured': snap_info['secured'],
2290 'timeToLive': human_ttl_timestamp,
2291 'timestamp': human_timestamp},
2292 'source_reference': {'source-id': snap_info['device']}}
2293 manageable_snaps.append(snap_dict)
2294
2295 # If snapshot list is populated, perform filtering on user params
2296 if len(manageable_snaps) > 0:
2297 # Order snapshots by source deviceID and not snapshot name
2298 manageable_snaps = sorted(
2299 manageable_snaps,
2300 key=lambda k: k['source_reference']['source-id'])
2301 # If sort keys selected, determine if by size or reference, and
2302 # direction of sort
2303 if sort_keys:
2304 reverse = False
2305 if sort_dirs:
2306 if 'desc' in sort_dirs[0]:
2307 reverse = True
2308 if sort_keys[0] == 'size':
2309 manageable_snaps = sorted(manageable_snaps,
2310 key=lambda k: k['size'],
2311 reverse=reverse)
2312 if sort_keys[0] == 'reference':
2313 manageable_snaps = sorted(manageable_snaps,
2314 key=lambda k: k['reference'][
2315 'source-name'],
2316 reverse=reverse)
2317
2318 # If marker provided, return only manageable volumes after marker
2319 if marker:
2320 snap_index = None
2321 for snap in manageable_snaps:
2322 if snap['reference']['source-name'] == marker:
2323 snap_index = manageable_snaps.index(snap)
2324 if snap_index:
2325 manageable_snaps = manageable_snaps[snap_index:]
2326 else:
2327 msg = (_("Snapshot marker %(marker)s not found, marker "
2328 "provided must be a valid VMAX snapshot ID") %
2329 {'marker': marker})
2330 raise exception.VolumeBackendAPIException(msg)
2331
2332 # If offset or limit provided, offset or limit result list
2333 if offset:
2334 manageable_snaps = manageable_snaps[offset:]
2335 if limit:
2336 manageable_snaps = manageable_snaps[:limit]
2337
2338 return manageable_snaps
2339
2143 def retype(self, volume, new_type, host): 2340 def retype(self, volume, new_type, host):
2144 """Migrate volume to another host using retype. 2341 """Migrate volume to another host using retype.
2145 2342
diff --git a/cinder/volume/drivers/dell_emc/vmax/fc.py b/cinder/volume/drivers/dell_emc/vmax/fc.py
index 86bb7b1..474f070 100644
--- a/cinder/volume/drivers/dell_emc/vmax/fc.py
+++ b/cinder/volume/drivers/dell_emc/vmax/fc.py
@@ -93,6 +93,8 @@ class VMAXFCDriver(san.SanDriver, driver.FibreChannelDriver):
93 3.2.0 - Support for retyping replicated volumes (bp 93 3.2.0 - Support for retyping replicated volumes (bp
94 vmax-retype-replicated-volumes) 94 vmax-retype-replicated-volumes)
95 - Support for multiattach volumes (bp vmax-allow-multi-attach) 95 - Support for multiattach volumes (bp vmax-allow-multi-attach)
96 - Support for list manageable volumes and snapshots
97 (bp/vmax-list-manage-existing)
96 """ 98 """
97 99
98 VERSION = "3.2.0" 100 VERSION = "3.2.0"
@@ -521,6 +523,40 @@ class VMAXFCDriver(san.SanDriver, driver.FibreChannelDriver):
521 """ 523 """
522 self.common.unmanage_snapshot(snapshot) 524 self.common.unmanage_snapshot(snapshot)
523 525
526 def get_manageable_volumes(self, cinder_volumes, marker, limit, offset,
527 sort_keys, sort_dirs):
528 """Lists all manageable volumes.
529
530 :param cinder_volumes: List of currently managed Cinder volumes.
531 Unused in driver.
532 :param marker: Begin returning volumes that appear later in the volume
533 list than that represented by this reference.
534 :param limit: Maximum number of volumes to return. Default=1000.
535 :param offset: Number of volumes to skip after marker.
536 :param sort_keys: Results sort key. Valid keys: size, reference.
537 :param sort_dirs: Results sort direction. Valid dirs: asc, desc.
538 :return: List of dicts containing all manageable volumes.
539 """
540 return self.common.get_manageable_volumes(marker, limit, offset,
541 sort_keys, sort_dirs)
542
543 def get_manageable_snapshots(self, cinder_snapshots, marker, limit, offset,
544 sort_keys, sort_dirs):
545 """Lists all manageable snapshots.
546
547 :param cinder_snapshots: List of currently managed Cinder snapshots.
548 Unused in driver.
549 :param marker: Begin returning volumes that appear later in the
550 snapshot list than that represented by this reference.
551 :param limit: Maximum number of snapshots to return. Default=1000.
552 :param offset: Number of snapshots to skip after marker.
553 :param sort_keys: Results sort key. Valid keys: size, reference.
554 :param sort_dirs: Results sort direction. Valid dirs: asc, desc.
555 :return: List of dicts containing all manageable snapshots.
556 """
557 return self.common.get_manageable_snapshots(marker, limit, offset,
558 sort_keys, sort_dirs)
559
524 def retype(self, ctxt, volume, new_type, diff, host): 560 def retype(self, ctxt, volume, new_type, diff, host):
525 """Migrate volume to another host using retype. 561 """Migrate volume to another host using retype.
526 562
diff --git a/cinder/volume/drivers/dell_emc/vmax/iscsi.py b/cinder/volume/drivers/dell_emc/vmax/iscsi.py
index bfab062..593a5c2 100644
--- a/cinder/volume/drivers/dell_emc/vmax/iscsi.py
+++ b/cinder/volume/drivers/dell_emc/vmax/iscsi.py
@@ -98,6 +98,8 @@ class VMAXISCSIDriver(san.SanISCSIDriver):
98 3.2.0 - Support for retyping replicated volumes (bp 98 3.2.0 - Support for retyping replicated volumes (bp
99 vmax-retype-replicated-volumes) 99 vmax-retype-replicated-volumes)
100 - Support for multiattach volumes (bp vmax-allow-multi-attach) 100 - Support for multiattach volumes (bp vmax-allow-multi-attach)
101 - Support for list manageable volumes and snapshots
102 (bp/vmax-list-manage-existing)
101 """ 103 """
102 104
103 VERSION = "3.2.0" 105 VERSION = "3.2.0"
@@ -440,6 +442,40 @@ class VMAXISCSIDriver(san.SanISCSIDriver):
440 """ 442 """
441 self.common.unmanage_snapshot(snapshot) 443 self.common.unmanage_snapshot(snapshot)
442 444
445 def get_manageable_volumes(self, cinder_volumes, marker, limit, offset,
446 sort_keys, sort_dirs):
447 """Lists all manageable volumes.
448
449 :param cinder_volumes: List of currently managed Cinder volumes.
450 Unused in driver.
451 :param marker: Begin returning volumes that appear later in the volume
452 list than that represented by this reference.
453 :param limit: Maximum number of volumes to return. Default=1000.
454 :param offset: Number of volumes to skip after marker.
455 :param sort_keys: Results sort key. Valid keys: size, reference.
456 :param sort_dirs: Results sort direction. Valid dirs: asc, desc.
457 :return: List of dicts containing all manageable volumes.
458 """
459 return self.common.get_manageable_volumes(marker, limit, offset,
460 sort_keys, sort_dirs)
461
462 def get_manageable_snapshots(self, cinder_snapshots, marker, limit, offset,
463 sort_keys, sort_dirs):
464 """Lists all manageable snapshots.
465
466 :param cinder_snapshots: List of currently managed Cinder snapshots.
467 Unused in driver.
468 :param marker: Begin returning volumes that appear later in the
469 snapshot list than that represented by this reference.
470 :param limit: Maximum number of snapshots to return. Default=1000.
471 :param offset: Number of snapshots to skip after marker.
472 :param sort_keys: Results sort key. Valid keys: size, reference.
473 :param sort_dirs: Results sort direction. Valid dirs: asc, desc.
474 :return: List of dicts containing all manageable snapshots.
475 """
476 return self.common.get_manageable_snapshots(marker, limit, offset,
477 sort_keys, sort_dirs)
478
443 def retype(self, ctxt, volume, new_type, diff, host): 479 def retype(self, ctxt, volume, new_type, diff, host):
444 """Migrate volume to another host using retype. 480 """Migrate volume to another host using retype.
445 481
diff --git a/cinder/volume/drivers/dell_emc/vmax/rest.py b/cinder/volume/drivers/dell_emc/vmax/rest.py
index 740794d..6ce4050 100644
--- a/cinder/volume/drivers/dell_emc/vmax/rest.py
+++ b/cinder/volume/drivers/dell_emc/vmax/rest.py
@@ -1011,6 +1011,72 @@ class VMAXRest(object):
1011 pass 1011 pass
1012 return device_ids 1012 return device_ids
1013 1013
1014 def get_private_volume_list(self, array, params=None):
1015 """Retrieve list with volume details.
1016
1017 :param array: the array serial number
1018 :param params: filter parameters
1019 :returns: list -- dicts with volume information
1020 """
1021 volumes = []
1022 volume_info = self.get_resource(
1023 array, SLOPROVISIONING, 'volume', params=params,
1024 private='/private')
1025 try:
1026 volumes = volume_info['resultList']['result']
1027 iterator_id = volume_info['id']
1028 volume_count = volume_info['count']
1029 max_page_size = volume_info['maxPageSize']
1030 start_position = volume_info['resultList']['from']
1031 end_position = volume_info['resultList']['to']
1032 except (KeyError, TypeError):
1033 return volumes
1034
1035 if volume_count > max_page_size:
1036 LOG.info("More entries exist in the result list, retrieving "
1037 "remainder of results from iterator.")
1038
1039 start_position += 1000
1040 end_position += 1000
1041 iterator_response = self.get_iterator_page_list(
1042 iterator_id, volume_count, start_position, end_position)
1043
1044 volumes += iterator_response
1045
1046 return volumes
1047
1048 def get_iterator_page_list(self, iterator_id, result_count, start_position,
1049 end_position):
1050 """Iterate through response if more than one page available.
1051
1052 :param iterator_id: the iterator ID
1053 :param result_count: the amount of results in the iterator
1054 :param start_position: position to begin iterator from
1055 :param end_position: position to stop iterator
1056 :return: list -- merged results from multiple pages
1057 """
1058 iterator_result = []
1059 has_more_entries = True
1060
1061 while has_more_entries:
1062 if start_position <= result_count <= end_position:
1063 end_position = result_count
1064 has_more_entries = False
1065
1066 params = {'to': start_position, 'from': end_position}
1067 target_uri = ('/common/Iterator/%(iterator_id)s/page' % {
1068 'iterator_id': iterator_id})
1069 iterator_response = self._get_request(target_uri, 'iterator',
1070 params)
1071 try:
1072 iterator_result += iterator_response['result']
1073 start_position += 1000
1074 end_position += 1000
1075 except (KeyError, TypeError):
1076 pass
1077
1078 return iterator_result
1079
1014 def _modify_volume(self, array, device_id, payload): 1080 def _modify_volume(self, array, device_id, payload):
1015 """Modify a volume (PUT operation). 1081 """Modify a volume (PUT operation).
1016 1082
diff --git a/cinder/volume/drivers/dell_emc/vmax/utils.py b/cinder/volume/drivers/dell_emc/vmax/utils.py
index 370c5bb..f2a3995 100644
--- a/cinder/volume/drivers/dell_emc/vmax/utils.py
+++ b/cinder/volume/drivers/dell_emc/vmax/utils.py
@@ -879,3 +879,81 @@ class VMAXUtils(object):
879 is_tgt_multiattach = vol_utils.is_replicated_str( 879 is_tgt_multiattach = vol_utils.is_replicated_str(
880 new_type_extra_specs.get('multiattach')) 880 new_type_extra_specs.get('multiattach'))
881 return is_src_multiattach != is_tgt_multiattach 881 return is_src_multiattach != is_tgt_multiattach
882
883 @staticmethod
884 def is_volume_manageable(source_vol):
885 """Check if a volume with verbose description is valid for management.
886
887 :param source_vol: the verbose volume dict
888 :return: bool True/False
889 """
890 vol_head = source_vol['volumeHeader']
891
892 # VMAX disk geometry uses cylinders, so volume sizes are matched to
893 # the nearest full cylinder size: 1GB = 547cyl = 1026MB
894 if vol_head['capMB'] < 1026 or not vol_head['capGB'].is_integer():
895 return False
896
897 if (vol_head['numSymDevMaskingViews'] > 0 or
898 vol_head['mapped'] is True or
899 source_vol['maskingInfo']['masked'] is True):
900 return False
901
902 if (vol_head['status'] != 'Ready' or
903 vol_head['serviceState'] != 'Normal' or
904 vol_head['emulationType'] != 'FBA' or
905 vol_head['configuration'] != 'TDEV' or
906 vol_head['system_resource'] is True or
907 vol_head['private'] is True or
908 vol_head['encapsulated'] is True or
909 vol_head['reservationInfo']['reserved'] is True):
910 return False
911
912 for key, value in source_vol['rdfInfo'].items():
913 if value is True:
914 return False
915
916 if source_vol['timeFinderInfo']['snapVXTgt'] is True:
917 return False
918
919 if vol_head['nameModifier'][0:3] == 'OS-':
920 return False
921
922 return True
923
924 @staticmethod
925 def is_snapshot_manageable(source_vol):
926 """Check if a volume with snapshot description is valid for management.
927
928 :param source_vol: the verbose volume dict
929 :return: bool True/False
930 """
931 vol_head = source_vol['volumeHeader']
932
933 if not source_vol['timeFinderInfo']['snapVXSrc']:
934 return False
935
936 # VMAX disk geometry uses cylinders, so volume sizes are matched to
937 # the nearest full cylinder size: 1GB = 547cyl = 1026MB
938 if (vol_head['capMB'] < 1026 or
939 not vol_head['capGB'].is_integer()):
940 return False
941
942 if (vol_head['emulationType'] != 'FBA' or
943 vol_head['configuration'] != 'TDEV' or
944 vol_head['private'] is True or
945 vol_head['system_resource'] is True):
946 return False
947
948 snap_gen_info = (source_vol['timeFinderInfo']['snapVXSession'][0][
949 'srcSnapshotGenInfo'][0]['snapshotHeader'])
950
951 if (snap_gen_info['snapshotName'][0:3] == 'OS-' or
952 snap_gen_info['snapshotName'][0:5] == 'temp-'):
953 return False
954
955 if (snap_gen_info['expired'] is True
956 or snap_gen_info['generation'] > 0):
957 return False
958
959 return True
diff --git a/releasenotes/notes/vmax-list-manageable-vols-snaps-6a7f5aa114fae8f3.yaml b/releasenotes/notes/vmax-list-manageable-vols-snaps-6a7f5aa114fae8f3.yaml
new file mode 100644
index 0000000..568bfd0
--- /dev/null
+++ b/releasenotes/notes/vmax-list-manageable-vols-snaps-6a7f5aa114fae8f3.yaml
@@ -0,0 +1,4 @@
1---
2features:
3 - Dell EMC VMAX driver has added list manageable volumes and snapshots
4 support.