Browse Source

Merge "VMAX driver - VMAX list manageable volumes and snapshots."

tags/13.0.0.0b3
Zuul 11 months ago
parent
commit
4335b3e544

+ 414
- 0
cinder/tests/unit/volume/drivers/dell_emc/vmax/test_vmax.py View File

@@ -683,6 +683,232 @@ class VMAXCommonData(object):
683 683
 
684 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
 
687 913
 class FakeLookupService(object):
688 914
     def get_device_mapping_from_network(self, initiator_wwns, target_wwns):
@@ -1543,6 +1769,22 @@ class VMAXUtilsTest(test.TestCase):
1543 1769
         self.assertFalse(self.utils.change_multiattach(
1544 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
 
1547 1789
 class VMAXRestTest(test.TestCase):
1548 1790
     def setUp(self):
@@ -2940,6 +3182,68 @@ class VMAXRestTest(test.TestCase):
2940 3182
             rename=True, new_snap_name=new_snap_backend_name)
2941 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
 
2944 3248
 class VMAXProvisionTest(test.TestCase):
2945 3249
     def setUp(self):
@@ -5179,6 +5483,116 @@ class VMAXCommonTest(test.TestCase):
5179 5483
         initiator_check = self.common._get_initiator_check_flag()
5180 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
 
5183 5597
 class VMAXFCTest(test.TestCase):
5184 5598
     def setUp(self):

+ 197
- 0
cinder/volume/drivers/dell_emc/vmax/common.py View File

@@ -15,9 +15,11 @@
15 15
 
16 16
 import ast
17 17
 from copy import deepcopy
18
+import math
18 19
 import os.path
19 20
 import random
20 21
 import sys
22
+import time
21 23
 
22 24
 from oslo_config import cfg
23 25
 from oslo_log import log as logging
@@ -2140,6 +2142,201 @@ class VMAXCommon(object):
2140 2142
                  "OpenStack but still remains on VMAX source "
2141 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 2340
     def retype(self, volume, new_type, host):
2144 2341
         """Migrate volume to another host using retype.
2145 2342
 

+ 36
- 0
cinder/volume/drivers/dell_emc/vmax/fc.py View File

@@ -93,6 +93,8 @@ class VMAXFCDriver(san.SanDriver, driver.FibreChannelDriver):
93 93
         3.2.0 - Support for retyping replicated volumes (bp
94 94
                 vmax-retype-replicated-volumes)
95 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 100
     VERSION = "3.2.0"
@@ -521,6 +523,40 @@ class VMAXFCDriver(san.SanDriver, driver.FibreChannelDriver):
521 523
         """
522 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 560
     def retype(self, ctxt, volume, new_type, diff, host):
525 561
         """Migrate volume to another host using retype.
526 562
 

+ 36
- 0
cinder/volume/drivers/dell_emc/vmax/iscsi.py View File

@@ -98,6 +98,8 @@ class VMAXISCSIDriver(san.SanISCSIDriver):
98 98
         3.2.0 - Support for retyping replicated volumes (bp
99 99
                 vmax-retype-replicated-volumes)
100 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 105
     VERSION = "3.2.0"
@@ -440,6 +442,40 @@ class VMAXISCSIDriver(san.SanISCSIDriver):
440 442
         """
441 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 479
     def retype(self, ctxt, volume, new_type, diff, host):
444 480
         """Migrate volume to another host using retype.
445 481
 

+ 66
- 0
cinder/volume/drivers/dell_emc/vmax/rest.py View File

@@ -1011,6 +1011,72 @@ class VMAXRest(object):
1011 1011
             pass
1012 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 1080
     def _modify_volume(self, array, device_id, payload):
1015 1081
         """Modify a volume (PUT operation).
1016 1082
 

+ 78
- 0
cinder/volume/drivers/dell_emc/vmax/utils.py View File

@@ -879,3 +879,81 @@ class VMAXUtils(object):
879 879
         is_tgt_multiattach = vol_utils.is_replicated_str(
880 880
             new_type_extra_specs.get('multiattach'))
881 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

+ 4
- 0
releasenotes/notes/vmax-list-manageable-vols-snaps-6a7f5aa114fae8f3.yaml View File

@@ -0,0 +1,4 @@
1
+---
2
+features:
3
+  - Dell EMC VMAX driver has added list manageable volumes and snapshots
4
+    support.

Loading…
Cancel
Save