Browse Source

Merge "Address review comments for MacroSAN driver"

changes/80/639180/35
Zuul 1 week ago
parent
commit
a7f1301914

+ 102
- 24
cinder/tests/unit/test_macrosan_drivers.py View File

@@ -34,7 +34,6 @@ test_volume = (
34 34
     UserDict({'name': 'volume-728ec287-bf30-4d2d-98a8-7f1bed3f59ce',
35 35
               'volume_name': 'test',
36 36
               'id': '728ec287-bf30-4d2d-98a8-7f1bed3f59ce',
37
-              'volume_id': '728ec287-bf30-4d2d-98a8-7f1bed3f59ce',
38 37
               'provider_auth': None,
39 38
               'project_id': 'project',
40 39
               'display_name': 'test',
@@ -45,7 +44,7 @@ test_volume = (
45 44
               'macrosan uuid:0x00b34201-025b0000-46b35ae7-b7deec47'}))
46 45
 
47 46
 test_volume.size = 10
48
-test_volume.volume_type_id = None
47
+test_volume.volume_type_id = '36674caf-5314-468a-a8cb-baab4f71fe44'
49 48
 test_volume.volume_attachment = []
50 49
 
51 50
 test_migrate_volume = {
@@ -58,7 +57,7 @@ test_migrate_volume = {
58 57
     'project_id': 'project',
59 58
     'display_name': 'test',
60 59
     'display_description': 'test',
61
-    'volume_type_id': None,
60
+    'volume_type_id': '36674caf-5314-468a-a8cb-baab4f71fe44',
62 61
     '_name_id': None,
63 62
     'host': 'controller@macrosan#MacroSAN',
64 63
     'provider_location':
@@ -73,7 +72,7 @@ test_snap = {'name': 'volume-728ec287-bf30-4d2d-98a8-7f1bed3f59ce',
73 72
              'project_id': 'project',
74 73
              'display_name': 'test',
75 74
              'display_description': 'test volume',
76
-             'volume_type_id': None,
75
+             'volume_type_id': '36674caf-5314-468a-a8cb-baab4f71fe44',
77 76
              'provider_location': 'pointid: 1',
78 77
              'volume_size': 10,
79 78
              'volume': test_volume}
@@ -106,6 +105,17 @@ expected_iscsi_properties = {'target_discovered': False,
106 105
                              '728ec287-bf30-4d2d-98a8-7f1bed3f59ce'
107 106
                              }
108 107
 
108
+expected_iscsi_connection_data = {
109
+    'client': 'devstack',
110
+    'ports': [{'ip': '192.168.251.1',
111
+               'port': 'eth-1:0:0',
112
+               'port_name': 'iSCSI-Target-1:0:0',
113
+               'target': 'iqn.2010-05.com.macrosan.target:controller'},
114
+              {'ip': '192.168.251.2',
115
+               'port': 'eth-2:0:0',
116
+               'port_name': 'iSCSI-Target-2:0:0',
117
+               'target': 'iqn.2010-05.com.macrosan.target:controller'}]}
118
+
109 119
 expected_initr_port_map_tgtexist = {
110 120
     '21:00:00:24:ff:20:03:ec': [{'port_name': 'FC-Target-1:1:1',
111 121
                                  'wwn': '50:0b:34:20:01:00:18:05'},
@@ -188,12 +198,10 @@ class FakeMacroSANISCSIDriver(driver.MacroSANISCSIDriver):
188 198
     def _get_client_name(self, host):
189 199
         return 'devstack'
190 200
 
191
-    @utils.synchronized('MacroSAN-Attach', external=True)
192 201
     def _attach_volume(self, context, volume, properties, remote=False):
193 202
         return super(FakeMacroSANISCSIDriver, self)._attach_volume(
194 203
             context, volume, properties, remote)
195 204
 
196
-    @utils.synchronized('MacroSAN-Attach', external=True)
197 205
     def _detach_volume(self, context, attach_info, volume,
198 206
                        properties, force=False, remote=False,
199 207
                        ignore_errors=True):
@@ -383,8 +391,7 @@ class MacroSANISCSIDriverTestCase(test.TestCase):
383 391
     def setUp(self):
384 392
         super(MacroSANISCSIDriverTestCase, self).setUp()
385 393
         self.configuration = mock.Mock(spec=conf.Configuration)
386
-        self.configuration.san_ip = \
387
-            "172.192.251.1, 172.192.251.2"
394
+        self.configuration.san_ip = "172.192.251.1, 172.192.251.2"
388 395
         self.configuration.san_login = "openstack"
389 396
         self.configuration.san_password = "passwd"
390 397
         self.configuration.macrosan_sdas_ipaddrs = None
@@ -398,9 +405,8 @@ class MacroSANISCSIDriverTestCase(test.TestCase):
398 405
         self.configuration.macrosan_snapshot_resource_ratio = 0.3
399 406
         self.configuration.macrosan_log_timing = True
400 407
         self.configuration.macrosan_client = \
401
-            ['devstack; decive1; "eth-1:0:0"; "eth-2:0:0"']
402
-        self.configuration.macrosan_client_default = \
403
-            "eth-1:0:0;eth-2:0:0"
408
+            ['devstack; device1; "eth-1:0:0"; "eth-2:0:0"']
409
+        self.configuration.macrosan_client_default = "eth-1:0:0;eth-2:0:0"
404 410
         self.driver = FakeMacroSANISCSIDriver(configuration=self.configuration)
405 411
         self.driver.do_setup()
406 412
 
@@ -465,12 +471,19 @@ class MacroSANISCSIDriverTestCase(test.TestCase):
465 471
         actual = ret['provider_location']
466 472
         self.assertEqual(test_volume['provider_location'], actual)
467 473
 
474
+    @mock.patch.object(volume_types, 'get_volume_type',
475
+                       return_value={'qos_specs_id':
476
+                                     '99f3d240-1b20-4b7b-9321-c6b8b86243ff',
477
+                                     'extra_specs': {}})
478
+    @mock.patch.object(qos_specs, 'get_qos_specs',
479
+                       return_value={'specs': {'qos-strategy': 'QoS-1'}})
468 480
     @mock.patch.object(socket, 'gethostname', return_value='controller')
469 481
     @mock.patch.object(utils, 'brick_get_connector',
470 482
                        return_value=DummyBrickGetConnector())
471 483
     @mock.patch.object(volutils, 'copy_volume', return_value=None)
472 484
     @mock.patch.object(os.path, 'realpath', return_value=None)
473
-    def test_create_cloned_volume(self, mock_hostname,
485
+    def test_create_cloned_volume(self, mock_volume_types, mock_qos,
486
+                                  mock_hostname,
474 487
                                   mock_brick_get_connector,
475 488
                                   mock_copy_volume,
476 489
                                   mock_os_path):
@@ -514,7 +527,9 @@ class MacroSANISCSIDriverTestCase(test.TestCase):
514 527
     @mock.patch.object(qos_specs, 'get_qos_specs',
515 528
                        return_value={'specs': {'qos-strategy': 'QoS-1'}})
516 529
     def test_terminate_connection(self, mock_volume_type, mock_qos):
517
-        self.driver.terminate_connection(test_volume, test_connector)
530
+        ret = self.driver.terminate_connection(test_volume, test_connector)
531
+        self.assertEqual({'driver_volume_type': 'iSCSI',
532
+                          'data': expected_iscsi_connection_data}, ret)
518 533
 
519 534
     def test_get_raid_list(self):
520 535
         expected = ["RAID-1"]
@@ -564,12 +579,19 @@ class MacroSANISCSIDriverTestCase(test.TestCase):
564 579
                           self.driver.create_volume_from_snapshot,
565 580
                           test_volume, test_snap)
566 581
 
582
+    @mock.patch.object(volume_types, 'get_volume_type',
583
+                       return_value={'qos_specs_id':
584
+                                     '99f3d240-1b20-4b7b-9321-c6b8b86243ff',
585
+                                     'extra_specs': {}})
586
+    @mock.patch.object(qos_specs, 'get_qos_specs',
587
+                       return_value={'specs': {'qos-strategy': 'QoS-1'}})
567 588
     @mock.patch.object(socket, 'gethostname', return_value='controller')
568 589
     @mock.patch.object(utils, 'brick_get_connector',
569 590
                        return_value=DummyBrickGetConnector())
570 591
     @mock.patch.object(volutils, 'copy_volume', return_value=None)
571 592
     @mock.patch.object(os.path, 'realpath', return_value=None)
572
-    def test_create_cloned_volume_fail(self, mock_hostname,
593
+    def test_create_cloned_volume_fail(self, mock_volume_types, mock_qos,
594
+                                       mock_hostname,
573 595
                                        mock_brick_get_connector,
574 596
                                        mock_copy_volume,
575 597
                                        mock_os_path):
@@ -630,7 +652,7 @@ class MacroSANFCDriverTestCase(test.TestCase):
630 652
         self.configuration.macrosan_fc_keep_mapped_ports = True
631 653
         self.configuration.macrosan_host_name = 'devstack'
632 654
         self.configuration.macrosan_client = \
633
-            ['devstack; decive1; "eth-1:0:0"; "eth-2:0:0"']
655
+            ['devstack; device1; "eth-1:0:0"; "eth-2:0:0"']
634 656
         self.configuration.macrosan_client_default = \
635 657
             "eth-1:0:0;eth-2:0:0"
636 658
         self.driver = FakeMacroSANFCDriver(configuration=self.configuration)
@@ -647,19 +669,40 @@ class MacroSANFCDriverTestCase(test.TestCase):
647 669
                                               test_connector['wwpns'])
648 670
         self.assertEqual(expected_initr_port_map_tgtexist, ret)
649 671
 
650
-    def test_initialize_connection(self):
672
+    @mock.patch.object(volume_types, 'get_volume_type',
673
+                       return_value={'qos_specs_id':
674
+                                     '99f3d240-1b20-4b7b-9321-c6b8b86243ff',
675
+                                     'extra_specs': {}})
676
+    @mock.patch.object(qos_specs, 'get_qos_specs',
677
+                       return_value={'specs': {'qos-strategy': 'QoS-1'}})
678
+    def test_initialize_connection(self, mock_volume_types, mock_qos):
651 679
         ret = self.driver.initialize_connection(test_volume, test_connector)
652 680
         self.assertEqual(expected_fctgtexist_properties, ret['data'])
653 681
 
654
-    def test_terminate_connection(self):
655
-        self.driver.terminate_connection(test_volume, test_connector)
682
+    @mock.patch.object(volume_types, 'get_volume_type',
683
+                       return_value={'qos_specs_id':
684
+                                     '99f3d240-1b20-4b7b-9321-c6b8b86243ff',
685
+                                     'extra_specs': {}})
686
+    @mock.patch.object(qos_specs, 'get_qos_specs',
687
+                       return_value={'specs': {'qos-strategy': 'QoS-1'}})
688
+    def test_terminate_connection(self, mock_volume_types, mock_qos):
689
+        ret = self.driver.terminate_connection(test_volume, test_connector)
690
+        self.assertEqual({'driver_volume_type': 'fibre_channel', 'data': {}},
691
+                         ret)
656 692
 
693
+    @mock.patch.object(volume_types, 'get_volume_type',
694
+                       return_value={'qos_specs_id':
695
+                                     '99f3d240-1b20-4b7b-9321-c6b8b86243ff',
696
+                                     'extra_specs': {}})
697
+    @mock.patch.object(qos_specs, 'get_qos_specs',
698
+                       return_value={'specs': {'qos-strategy': 'QoS-1'}})
657 699
     @mock.patch.object(socket, 'gethostname', return_value='controller')
658 700
     @mock.patch.object(utils, 'brick_get_connector',
659 701
                        return_value=DummyBrickGetConnector())
660 702
     @mock.patch.object(volutils, 'copy_volume', return_value=None)
661 703
     @mock.patch.object(os.path, 'realpath', return_value=None)
662
-    def test_create_volume_from_snapshot(self, mock_hostname,
704
+    def test_create_volume_from_snapshot(self, mock_volume_types, mock_qos,
705
+                                         mock_hostname,
663 706
                                          mock_brick_get_connector,
664 707
                                          mock_copy_volume,
665 708
                                          mock_os_path):
@@ -667,12 +710,20 @@ class MacroSANFCDriverTestCase(test.TestCase):
667 710
         actual = ret['provider_location']
668 711
         self.assertEqual(test_volume['provider_location'], actual)
669 712
 
713
+    @mock.patch.object(volume_types, 'get_volume_type',
714
+                       return_value={
715
+                           'qos_specs_id':
716
+                               '99f3d240-1b20-4b7b-9321-c6b8b86243ff',
717
+                           'extra_specs': {}})
718
+    @mock.patch.object(qos_specs, 'get_qos_specs',
719
+                       return_value={'specs': {'qos-strategy': 'QoS-1'}})
670 720
     @mock.patch.object(socket, 'gethostname', return_value='controller')
671 721
     @mock.patch.object(utils, 'brick_get_connector',
672 722
                        return_value=DummyBrickGetConnector())
673 723
     @mock.patch.object(volutils, 'copy_volume', return_value=None)
674 724
     @mock.patch.object(os.path, 'realpath', return_value=None)
675
-    def test_create_cloned_volume(self, mock_hostname,
725
+    def test_create_cloned_volume(self, mock_volume_types, mock_qos,
726
+                                  mock_hostname,
676 727
                                   mock_brick_get_connector,
677 728
                                   mock_copy_volume,
678 729
                                   mock_os_path):
@@ -681,12 +732,20 @@ class MacroSANFCDriverTestCase(test.TestCase):
681 732
         actual = ret['provider_location']
682 733
         self.assertEqual(test_volume['provider_location'], actual)
683 734
 
735
+    @mock.patch.object(volume_types, 'get_volume_type',
736
+                       return_value={'qos_specs_id':
737
+                                     '99f3d240-1b20-4b7b-9321-c6b8b86243ff',
738
+                                     'extra_specs': {}})
739
+    @mock.patch.object(qos_specs, 'get_qos_specs',
740
+                       return_value={'specs': {'qos-strategy': 'QoS-1'}})
684 741
     @mock.patch.object(socket, 'gethostname', return_value='controller')
685 742
     @mock.patch.object(utils, 'brick_get_connector',
686 743
                        return_value=DummyBrickGetConnector())
687 744
     @mock.patch.object(volutils, 'copy_volume', return_value=None)
688 745
     @mock.patch.object(os.path, 'realpath', return_value=None)
689
-    def test_create_volume_from_snapshot_fail(self, mock_hostname,
746
+    def test_create_volume_from_snapshot_fail(self, mock_volume_types,
747
+                                              mock_qos,
748
+                                              mock_hostname,
690 749
                                               mock_brick_get_connector,
691 750
                                               mock_copy_volume,
692 751
                                               mock_os_path):
@@ -695,12 +754,19 @@ class MacroSANFCDriverTestCase(test.TestCase):
695 754
                           self.driver.create_volume_from_snapshot,
696 755
                           test_volume, test_snap)
697 756
 
757
+    @mock.patch.object(volume_types, 'get_volume_type',
758
+                       return_value={'qos_specs_id':
759
+                                     '99f3d240-1b20-4b7b-9321-c6b8b86243ff',
760
+                                     'extra_specs': {}})
761
+    @mock.patch.object(qos_specs, 'get_qos_specs',
762
+                       return_value={'specs': {'qos-strategy': 'QoS-1'}})
698 763
     @mock.patch.object(socket, 'gethostname', return_value='controller')
699 764
     @mock.patch.object(utils, 'brick_get_connector',
700 765
                        return_value=DummyBrickGetConnector())
701 766
     @mock.patch.object(volutils, 'copy_volume', return_value=None)
702 767
     @mock.patch.object(os.path, 'realpath', return_value=None)
703
-    def test_create_cloned_volume_fail(self, mock_hostname,
768
+    def test_create_cloned_volume_fail(self, mock_volume_types, mock_qos,
769
+                                       mock_hostname,
704 770
                                        mock_brick_get_connector,
705 771
                                        mock_copy_volume,
706 772
                                        mock_os_path):
@@ -709,13 +775,25 @@ class MacroSANFCDriverTestCase(test.TestCase):
709 775
                           self.driver.create_cloned_volume,
710 776
                           test_volume, test_volume)
711 777
 
712
-    def test_initialize_connection_fail(self):
778
+    @mock.patch.object(volume_types, 'get_volume_type',
779
+                       return_value={'qos_specs_id':
780
+                                     '99f3d240-1b20-4b7b-9321-c6b8b86243ff',
781
+                                     'extra_specs': {}})
782
+    @mock.patch.object(qos_specs, 'get_qos_specs',
783
+                       return_value={'specs': {'qos-strategy': 'QoS-1'}})
784
+    def test_initialize_connection_fail(self, mock_volume_types, mock_qos):
713 785
         self.driver.client.cmd_fail = True
714 786
         self.assertRaises(exception.VolumeBackendAPIException,
715 787
                           self.driver.initialize_connection,
716 788
                           test_volume, test_connector)
717 789
 
718
-    def test_terminate_connection_fail(self):
790
+    @mock.patch.object(volume_types, 'get_volume_type',
791
+                       return_value={'qos_specs_id':
792
+                                     '99f3d240-1b20-4b7b-9321-c6b8b86243ff',
793
+                                     'extra_specs': {}})
794
+    @mock.patch.object(qos_specs, 'get_qos_specs',
795
+                       return_value={'specs': {'qos-strategy': 'QoS-1'}})
796
+    def test_terminate_connection_fail(self, mock_volume_types, mock_qos):
719 797
         self.driver.client.cmd_fail = True
720 798
         self.assertRaises(exception.VolumeBackendAPIException,
721 799
                           self.driver.terminate_connection,

+ 12
- 21
cinder/volume/drivers/macrosan/config.py View File

@@ -20,33 +20,25 @@ from oslo_config import cfg
20 20
 macrosan_opts = [
21 21
     # sdas login_info
22 22
     cfg.ListOpt('macrosan_sdas_ipaddrs',
23
-                default=None,
24 23
                 help="MacroSAN sdas devices' ip addresses"),
25 24
     cfg.StrOpt('macrosan_sdas_username',
26
-               default=None,
27
-               help=""),
25
+               help="MacroSAN sdas devices' username"),
28 26
     cfg.StrOpt('macrosan_sdas_password',
29
-               default=None,
30
-               help="",
31
-               secret=True),
27
+               secret=True,
28
+               help="MacroSAN sdas devices' password"),
32 29
     # replication login_info
33 30
     cfg.ListOpt('macrosan_replication_ipaddrs',
34
-                default=None,
35 31
                 help="MacroSAN replication devices' ip addresses"),
36 32
     cfg.StrOpt('macrosan_replication_username',
37
-               default=None,
38
-               help=""),
33
+               help="MacroSAN replication devices' username"),
39 34
     cfg.StrOpt('macrosan_replication_password',
40
-               default=None,
41
-               help="",
42
-               secret=True),
35
+               secret=True,
36
+               help="MacroSAN replication devices' password"),
43 37
     cfg.ListOpt('macrosan_replication_destination_ports',
44
-                default=None,
45 38
                 sample_default="eth-1:0/eth-1:1, eth-2:0/eth-2:1",
46 39
                 help="Slave device"),
47 40
     # device_features
48 41
     cfg.StrOpt('macrosan_pool', quotes=True,
49
-               default=None,
50 42
                help='Pool to use for volume creation'),
51 43
     cfg.IntOpt('macrosan_thin_lun_extent_size',
52 44
                default=8,
@@ -80,21 +72,20 @@ macrosan_opts = [
80 72
                      "item associated with the port is maintained."),
81 73
     # iscsi connection
82 74
     cfg.ListOpt('macrosan_client',
83
-                default=None,
84 75
                 help="""Macrosan iscsi_clients list.
85 76
                 You can configure multiple clients.
86 77
                 You can configure it in this format:
87 78
                 (host; client_name; sp1_iscsi_port; sp2_iscsi_port),
88 79
                 (host; client_name; sp1_iscsi_port; sp2_iscsi_port)
89 80
                 Important warning, Client_name has the following requirements:
90
-                [a-zA-Z0-9.-_:], the maximum number of characters is 31
81
+                    [a-zA-Z0-9.-_:], the maximum number of characters is 31
91 82
                 E.g:
92
-                (controller1; decive1; eth-1:0; eth-2:0),
93
-                (controller2; decive2; eth-1:0/eth-1:1; eth-2:0/eth-2:1),
83
+                (controller1; device1; eth-1:0; eth-2:0),
84
+                (controller2; device2; eth-1:0/eth-1:1; eth-2:0/eth-2:1),
94 85
                 """),
95 86
     cfg.StrOpt('macrosan_client_default',
96
-               default=None,
97
-               help="This is the default connection information for iscsi. "
87
+               help="This is the default connection ports' name for iscsi. "
98 88
                     "This default configuration is used "
99
-                    "when no host related information is obtained.")
89
+                    "when no host related information is obtained."
90
+                    "E.g: eth-1:0/eth-1:1; eth-2:0/eth-2:1")
100 91
 ]

+ 1
- 1
cinder/volume/drivers/macrosan/devop_client.py View File

@@ -109,7 +109,7 @@ class Client(object):
109 109
             'attr': 'existence',
110 110
             'name': name
111 111
         }
112
-        return self.send_request('get', '/lun', data=data)
112
+        return self.send_request(method='get', url='/lun', data=data)
113 113
 
114 114
     def snapshot_point_exists(self, lun_name, pointid):
115 115
         """Whether the snapshot point exists."""

+ 46
- 138
cinder/volume/drivers/macrosan/driver.py View File

@@ -14,17 +14,13 @@
14 14
 #    under the License.
15 15
 """Volume Drivers for MacroSAN SAN."""
16 16
 
17
-import base64
18 17
 from contextlib import contextmanager
19 18
 import math
20 19
 import re
21
-import six
22 20
 import socket
23 21
 import time
24 22
 import uuid
25 23
 
26
-from os_brick.initiator import connector as cn
27
-from os_brick.initiator import linuxfc
28 24
 from oslo_config import cfg
29 25
 from oslo_log import log as logging
30 26
 from oslo_utils import excutils
@@ -47,7 +43,7 @@ from cinder.volume import utils as volume_utils
47 43
 from cinder.volume import volume_types
48 44
 from cinder.zonemanager import utils as fczm_utils
49 45
 
50
-version = '1.0.0'
46
+version = '1.0.1'
51 47
 lock_name = 'MacroSAN'
52 48
 
53 49
 LOG = logging.getLogger(__name__)
@@ -63,22 +59,6 @@ def ignored(*exceptions):
63 59
         pass
64 60
 
65 61
 
66
-def _timing(fn):
67
-    def __timing(*vargs, **kv):
68
-        start = time.time()
69
-        if timing_on:
70
-            LOG.info('========== start %s', fn.__name__)
71
-
72
-        result = fn(*vargs, **kv)
73
-
74
-        if timing_on:
75
-            end = time.time()
76
-            LOG.info('========== end %(fname)s, cost: %(cost).2f secs',
77
-                     {'fname': fn.__name__, 'cost': end - start})
78
-        return result
79
-    return __timing
80
-
81
-
82 62
 def record_request_id(fn):
83 63
     def _record_request_id(*vargs, **kv):
84 64
         ctx = context.context.get_current()
@@ -93,14 +73,6 @@ def replication_synced(params):
93 73
             params['replication_mode'] == 'sync')
94 74
 
95 75
 
96
-def b64encode(s):
97
-    return base64.b64encode(six.b(s)).decode()
98
-
99
-
100
-def b64decode(s):
101
-    return base64.b64decode(six.b(s)).decode()
102
-
103
-
104 76
 class MacroSANBaseDriver(driver.VolumeDriver):
105 77
     """Base driver for MacroSAN SAN."""
106 78
 
@@ -182,15 +154,6 @@ class MacroSANBaseDriver(driver.VolumeDriver):
182 154
 
183 155
         self.initialize_iscsi_info()
184 156
 
185
-    @staticmethod
186
-    def get_driver_options():
187
-        """Return the oslo_config options specific to the driver."""
188
-        return config.macrosan_opts
189
-
190
-    @property
191
-    def _self_node_wwns(self):
192
-        return []
193
-
194 157
     def _size_str_to_int(self, size_in_g):
195 158
         if int(size_in_g) == 0:
196 159
             return 1
@@ -254,7 +217,7 @@ class MacroSANBaseDriver(driver.VolumeDriver):
254 217
                                     self.replica_login_info))
255 218
         self.device_uuid = self.client.get_device_uuid()
256 219
         self._do_setup()
257
-        LOG.info('MacroSAN Cinder Driver setup complete.')
220
+        LOG.debug('MacroSAN Cinder Driver setup complete.')
258 221
 
259 222
     def _do_setup(self):
260 223
         pass
@@ -448,14 +411,9 @@ class MacroSANBaseDriver(driver.VolumeDriver):
448 411
 
449 412
     @synchronized(lock_name)
450 413
     @record_request_id
451
-    @_timing
414
+    @utils.trace
452 415
     def create_volume(self, volume):
453 416
         """Create a volume."""
454
-        LOG.debug(('========== create volume, name: %(name)s,'
455
-                   'id: %(volume_id)s, size: %(size)s.'),
456
-                  {'name': volume['name'], 'volume_id': volume['id'],
457
-                   'size': volume['size']})
458
-
459 417
         name = volume['name']
460 418
         size = self._size_str_to_int(volume['size'])
461 419
         params = self._parse_volume_params(volume)
@@ -508,22 +466,21 @@ class MacroSANBaseDriver(driver.VolumeDriver):
508 466
 
509 467
     @synchronized(lock_name)
510 468
     @record_request_id
511
-    @_timing
469
+    @utils.trace
512 470
     def delete_volume(self, volume):
513 471
         """Delete a volume."""
514
-        LOG.debug('========== delete volume, id: %s.', volume['id'])
515 472
         name = self._volume_name(volume)
516 473
         params = self._parse_volume_params(volume)
517 474
         self._delete_volume(name, params)
518 475
 
519
-    @utils.synchronized('MacroSAN-Attach', external=True)
476
+    @utils.synchronized('MacroSAN-Attach-Detach', external=True)
520 477
     def _attach_volume(self, context, volume, properties, remote=False):
521 478
         return super(MacroSANBaseDriver, self)._attach_volume(context,
522 479
                                                               volume,
523 480
                                                               properties,
524 481
                                                               remote)
525 482
 
526
-    @utils.synchronized('MacroSAN-Attach', external=True)
483
+    @utils.synchronized('MacroSAN-Attach-Detach', external=True)
527 484
     def _detach_volume(self, context, attach_info, volume,
528 485
                        properties, force=False, remote=False,
529 486
                        ignore_errors=True):
@@ -568,14 +525,10 @@ class MacroSANBaseDriver(driver.VolumeDriver):
568 525
 
569 526
     @synchronized(lock_name)
570 527
     @record_request_id
571
-    @_timing
528
+    @utils.trace
572 529
     def create_snapshot(self, snapshot):
573 530
         """Create a snapshot."""
574 531
         volume = snapshot['volume']
575
-        LOG.debug(('========== create snapshot, snapshot id: %(snapshot_id)s,'
576
-                   ' volume id: %(volume_id)s, size: %(size)s.'),
577
-                  {'snapshot_id': snapshot['id'], 'volume_id': volume['id'],
578
-                   'size': volume['size']})
579 532
 
580 533
         snapshot_name = self._snapshot_name(snapshot['name'])
581 534
         volume_name = self._volume_name(volume)
@@ -601,7 +554,7 @@ class MacroSANBaseDriver(driver.VolumeDriver):
601 554
 
602 555
     @synchronized(lock_name)
603 556
     @record_request_id
604
-    @_timing
557
+    @utils.trace
605 558
     def delete_snapshot(self, snapshot):
606 559
         """Delete a snapshot."""
607 560
         volume = snapshot['volume']
@@ -612,10 +565,7 @@ class MacroSANBaseDriver(driver.VolumeDriver):
612 565
         m = re.findall(r'pointid: (\d+)', provider)
613 566
         if m is None:
614 567
             return
615
-        LOG.debug(('========== delete snapshot, snapshot id: %(snapshot_id)s,'
616
-                   ' pointid: %(point_id)s, volume id: %(volume_id)s.'),
617
-                  {'snapshot_id': snapshot['id'], 'point_id': m[0],
618
-                   'volume_id': volume['id']})
568
+
619 569
         snapshot_name = self._snapshot_name(snapshot['id'])
620 570
         volume_name = self._volume_name(volume)
621 571
         self._delete_snapshot(snapshot_name, volume_name, m[0])
@@ -626,35 +576,6 @@ class MacroSANBaseDriver(driver.VolumeDriver):
626 576
     def _terminate_connection(self, name, host, wwns):
627 577
         raise NotImplementedError
628 578
 
629
-    def _connect(self, name):
630
-        host = socket.gethostname()
631
-        conn = self._initialize_connection(name, host,
632
-                                           self._self_node_wwns)
633
-
634
-        device_scan_attempts = self.configuration.num_volume_device_scan_tries
635
-        protocol = conn['driver_volume_type']
636
-        connector = utils.brick_get_connector(
637
-            protocol,
638
-            use_multipath=self.use_multipath,
639
-            device_scan_attempts=device_scan_attempts,
640
-            conn=conn)
641
-        device = None
642
-        try:
643
-            device = connector.connect_volume(conn['data'])
644
-        except Exception:
645
-            with excutils.save_and_reraise_exception():
646
-                self._terminate_connection(name, host, self._self_node_wwns)
647
-
648
-        return {'conn': conn, 'device': device, 'connector': connector}
649
-
650
-    def _disconnect(self, conn, name):
651
-        connector = conn['connector']
652
-        connector.disconnect_volume(conn['conn']['data'],
653
-                                    conn['device'])
654
-
655
-        self._terminate_connection(name, socket.gethostname(),
656
-                                   self._self_node_wwns)
657
-
658 579
     def _create_volume_from_snapshot(self, vol_name, vol_size,
659 580
                                      vol_params, snp_name, pointid,
660 581
                                      snp_vol_name, snp_vol_size):
@@ -685,10 +606,9 @@ class MacroSANBaseDriver(driver.VolumeDriver):
685 606
 
686 607
     @synchronized(lock_name)
687 608
     @record_request_id
688
-    @_timing
609
+    @utils.trace
689 610
     def create_volume_from_snapshot(self, volume, snapshot):
690 611
         """Create a volume from a snapshot."""
691
-        LOG.debug('========== create volume from snapshot.')
692 612
         snapshot_volume = snapshot['volume']
693 613
         provider = snapshot['provider_location']
694 614
         m = re.findall(r'pointid: (\d+)', provider)
@@ -727,10 +647,9 @@ class MacroSANBaseDriver(driver.VolumeDriver):
727 647
             self._delete_snapshot(snp_name, src_vol_name, pointid)
728 648
 
729 649
     @record_request_id
730
-    @_timing
650
+    @utils.trace
731 651
     def create_cloned_volume(self, volume, src_vref):
732 652
         """Create a clone of the specified volume."""
733
-        LOG.debug('========== create cloned volume.')
734 653
         vol_name = volume['id']
735 654
         src_vol_name = self._volume_name(src_vref)
736 655
         snapshotid =\
@@ -768,13 +687,9 @@ class MacroSANBaseDriver(driver.VolumeDriver):
768 687
 
769 688
     @synchronized(lock_name)
770 689
     @record_request_id
771
-    @_timing
690
+    @utils.trace
772 691
     def extend_volume(self, volume, new_size):
773 692
         """Extend a volume."""
774
-        LOG.debug(('========== extend volume, id: %(volume_id)s,'
775
-                   'size: %(size)s.'),
776
-                  {'volume_id': volume['id'], 'size': new_size})
777
-
778 693
         name = self._volume_name(volume)
779 694
         moresize = self._size_str_to_int(new_size - int(volume['size']))
780 695
         params = self._parse_volume_params(volume)
@@ -839,14 +754,12 @@ class MacroSANBaseDriver(driver.VolumeDriver):
839 754
         self._stats = data
840 755
 
841 756
     @record_request_id
757
+    @utils.trace
842 758
     def update_migrated_volume(self, ctxt, volume, new_volume,
843 759
                                original_volume_status=None):
844 760
         """Return model update for migrated volume."""
845 761
         original_name = self._volume_name(volume)
846 762
         cur_name = self._volume_name(new_volume)
847
-        LOG.debug(('========== update migrated volume,'
848
-                   'volume: %(original_name)s, new_volume: %(cur_name)s'),
849
-                  {'original_name': original_name, 'cur_name': cur_name})
850 763
 
851 764
         if self.client.lun_exists(original_name):
852 765
             self.client.backup_lun_name_to_rename_file(cur_name, original_name)
@@ -866,7 +779,7 @@ class MacroSANBaseDriver(driver.VolumeDriver):
866 779
 
867 780
     @synchronized(lock_name)
868 781
     @record_request_id
869
-    @_timing
782
+    @utils.trace
870 783
     def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
871 784
         volume = snapshot['volume']
872 785
         provider = snapshot['provider_location']
@@ -905,13 +818,13 @@ class MacroSANBaseDriver(driver.VolumeDriver):
905 818
 
906 819
     @synchronized(lock_name)
907 820
     @record_request_id
908
-    @_timing
821
+    @utils.trace
909 822
     def manage_existing(self, volume, external_ref):
910 823
         vol_params = self._parse_volume_params(volume)
911 824
         self._check_volume_params(vol_params)
912 825
         if vol_params['qos-strategy']:
913 826
             raise exception.VolumeBackendAPIException(
914
-                data=_('Not support to import qos-strategy'))
827
+                data=_('Import qos-strategy not supported'))
915 828
 
916 829
         pool = volume_utils.extract_host(volume.host, 'pool')
917 830
         name, info, params = self._get_existing_lun_info(external_ref)
@@ -995,7 +908,7 @@ class MacroSANBaseDriver(driver.VolumeDriver):
995 908
 
996 909
     @synchronized(lock_name)
997 910
     @record_request_id
998
-    @_timing
911
+    @utils.trace
999 912
     def manage_existing_snapshot(self, snapshot, existing_ref):
1000 913
         volume = snapshot['volume']
1001 914
         src_name = self._get_existing_snapname(existing_ref).lstrip('_')
@@ -1042,7 +955,7 @@ class MacroSANBaseDriver(driver.VolumeDriver):
1042 955
 
1043 956
     @synchronized(lock_name)
1044 957
     @record_request_id
1045
-    @_timing
958
+    @utils.trace
1046 959
     def migrate_volume(self, ctxt, volume, host):
1047 960
         if not self.migration_valid(volume, host):
1048 961
             return False, None
@@ -1054,8 +967,11 @@ class MacroSANBaseDriver(driver.VolumeDriver):
1054 967
         owner = self.client.get_lun_sp(src_name)
1055 968
         pool = host['capabilities'].get('pool_name', self.pool)
1056 969
 
1057
-        LOG.info('host: %(host)s, backend: %(volume_backend_name)s',
1058
-                 {'host': host,
970
+        LOG.info('Migrating volume: %(volume), '
971
+                 'host: %(host)s, '
972
+                 'backend: %(volume_backend_name)s',
973
+                 {'volume': src_name,
974
+                  'host': host,
1059 975
                   'volume_backend_name': self.volume_backend_name})
1060 976
         self._create_volume(name, size, params, owner, pool)
1061 977
 
@@ -1106,8 +1022,10 @@ class MacroSANISCSIDriver(MacroSANBaseDriver, driver.ISCSIDriver):
1106 1022
     .. code-block:: none
1107 1023
 
1108 1024
         1.0.0 - Initial driver
1025
+        1.0.1 - Adjust some log level and text prompts; Remove some useless
1026
+        functions; Add Cinder trace decorator. #1837920
1109 1027
     """
1110
-    VERSION = "1.0.0"
1028
+    VERSION = "1.0.1"
1111 1029
 
1112 1030
     def __init__(self, *args, **kwargs):
1113 1031
         """Initialize the driver."""
@@ -1198,11 +1116,6 @@ class MacroSANISCSIDriver(MacroSANBaseDriver, driver.ISCSIDriver):
1198 1116
 
1199 1117
         return id_list.pop()
1200 1118
 
1201
-    @property
1202
-    def _self_node_wwns(self):
1203
-        connector = cn.ISCSIConnector(utils.get_root_helper())
1204
-        return [connector.get_initiator()]
1205
-
1206 1119
     def _initialize_connection(self, name, vol_params, host, wwns):
1207 1120
         client_name = self._get_client_name(host)
1208 1121
         wwn = wwns[0]
@@ -1242,11 +1155,9 @@ class MacroSANISCSIDriver(MacroSANBaseDriver, driver.ISCSIDriver):
1242 1155
 
1243 1156
     @synchronized(lock_name)
1244 1157
     @record_request_id
1245
-    @_timing
1158
+    @utils.trace
1246 1159
     def initialize_connection(self, volume, connector):
1247 1160
         """Allow connection to connector and return connection info."""
1248
-        LOG.debug('========== initialize_connection connector: %(connector)s',
1249
-                  {'connector': connector})
1250 1161
 
1251 1162
         name = self._volume_name(volume)
1252 1163
         params = self._parse_volume_params(volume)
@@ -1273,21 +1184,26 @@ class MacroSANISCSIDriver(MacroSANBaseDriver, driver.ISCSIDriver):
1273 1184
         if volume_params['sdas']:
1274 1185
             self._unmap_itl(self.sdas_client, client_name, wwns, ports, name)
1275 1186
 
1187
+        data = dict()
1188
+        data['ports'] = ports
1189
+        data['client'] = client_name
1190
+        return {'driver_volume_type': 'iSCSI', 'data': data}
1191
+
1276 1192
     @synchronized(lock_name)
1277 1193
     @record_request_id
1278
-    @_timing
1194
+    @utils.trace
1279 1195
     def terminate_connection(self, volume, connector, **kwargs):
1280 1196
         """Disallow connection from connector."""
1281
-        LOG.debug('========== terminate_connection %(connector)s',
1282
-                  {'connector': connector})
1283 1197
 
1284 1198
         name = self._volume_name(volume)
1199
+        conn = None
1285 1200
         if not connector:
1286 1201
             self.force_terminate_connection(name, True)
1287 1202
         else:
1288 1203
             params = self._parse_volume_params(volume)
1289
-            self._terminate_connection(name, params, connector['host'],
1290
-                                       [connector['initiator']])
1204
+            conn = self._terminate_connection(name, params, connector['host'],
1205
+                                              [connector['initiator']])
1206
+        return conn
1291 1207
 
1292 1208
     def _initialize_connection_snapshot(self, snp_name, connector):
1293 1209
         return self._initialize_connection(snp_name, None, connector['host'],
@@ -1307,8 +1223,10 @@ class MacroSANFCDriver(MacroSANBaseDriver, driver.FibreChannelDriver):
1307 1223
     .. code-block:: none
1308 1224
 
1309 1225
         1.0.0 - Initial driver
1226
+        1.0.1 - Adjust some log level and text prompts; Remove some useless
1227
+        functions; Add Cinder trace decorator. #1837920
1310 1228
     """
1311
-    VERSION = "1.0.0"
1229
+    VERSION = "1.0.1"
1312 1230
 
1313 1231
     def __init__(self, *args, **kwargs):
1314 1232
         """Initialize the driver."""
@@ -1333,11 +1251,6 @@ class MacroSANFCDriver(MacroSANBaseDriver, driver.FibreChannelDriver):
1333 1251
                 if port['port_name'] == '':
1334 1252
                     self.sdas_client.create_target(port['port'])
1335 1253
 
1336
-    @property
1337
-    def _self_node_wwns(self):
1338
-        fc = linuxfc.LinuxFibreChannel(utils.get_root_helper())
1339
-        return [self._format_wwn_with_colon(wwn) for wwn in fc.get_fc_wwpns()]
1340
-
1341 1254
     def _strip_wwn_colon(self, wwn_str):
1342 1255
         return wwn_str.replace(':', '')
1343 1256
 
@@ -1479,8 +1392,8 @@ class MacroSANFCDriver(MacroSANBaseDriver, driver.FibreChannelDriver):
1479 1392
 
1480 1393
         has_port_not_mapped, initr_port_map = (
1481 1394
             self._map_initr_tgt(self.client, client_name, wwns))
1482
-        LOG.info('====================initr_port_map %(initr_port_map)s',
1483
-                 {'initr_port_map': initr_port_map})
1395
+        LOG.debug('initr_port_map: %(initr_port_map)s',
1396
+                  {'initr_port_map': initr_port_map})
1484 1397
 
1485 1398
         if vol_params and vol_params['sdas']:
1486 1399
             sdas_has_port_not_mapped, sdas_initr_port_map = (
@@ -1488,9 +1401,8 @@ class MacroSANFCDriver(MacroSANBaseDriver, driver.FibreChannelDriver):
1488 1401
             lun_id = self._get_unused_lun_id(self.client, initr_port_map,
1489 1402
                                              self.sdas_client,
1490 1403
                                              sdas_initr_port_map)
1491
-            LOG.info('%(fr)sdas_initr_port_map %(sdas_initr_port_map)s',
1492
-                     {'fr': '=' * 10,
1493
-                      'sdas_initr_port_map': sdas_initr_port_map})
1404
+            LOG.debug('sdas_initr_port_map: %(sdas_initr_port_map)s',
1405
+                      {'sdas_initr_port_map': sdas_initr_port_map})
1494 1406
             self._map_itl(self.sdas_client, sdas_initr_port_map, name, lun_id)
1495 1407
 
1496 1408
             lun_id = self._map_itl(self.client, initr_port_map, name, lun_id)
@@ -1528,11 +1440,9 @@ class MacroSANFCDriver(MacroSANBaseDriver, driver.FibreChannelDriver):
1528 1440
 
1529 1441
     @synchronized(lock_name)
1530 1442
     @record_request_id
1531
-    @_timing
1443
+    @utils.trace
1532 1444
     def initialize_connection(self, volume, connector):
1533 1445
         """Allow connection to connector and return connection info."""
1534
-        LOG.debug('========== initialize_connection connector: %(connector)s',
1535
-                  {'connector': connector})
1536 1446
 
1537 1447
         name = self._volume_name(volume)
1538 1448
         params = self._parse_volume_params(volume)
@@ -1593,11 +1503,9 @@ class MacroSANFCDriver(MacroSANBaseDriver, driver.FibreChannelDriver):
1593 1503
 
1594 1504
     @synchronized(lock_name)
1595 1505
     @record_request_id
1596
-    @_timing
1506
+    @utils.trace
1597 1507
     def terminate_connection(self, volume, connector, **kwargs):
1598 1508
         """Disallow connection from connector."""
1599
-        LOG.debug('========== terminate_connection %(connector)s',
1600
-                  {'connector': connector})
1601 1509
 
1602 1510
         name = self._volume_name(volume)
1603 1511
         conn = None

+ 20
- 27
doc/source/configuration/block-storage/drivers/MacroSAN-storage-driver.rst View File

@@ -3,8 +3,7 @@ MacroSAN Fibre Channel and iSCSI drivers
3 3
 ==========================================
4 4
 
5 5
 The ``MacroSANFCDriver`` and ``MacroSANISCSIDriver`` Cinder drivers allow the
6
-MacroSAN Storage arrays to be used for Block Storage in
7
-OpenStack deployments.
6
+MacroSAN Storage arrays to be used for Block Storage in OpenStack deployments.
8 7
 
9 8
 System requirements
10 9
 ~~~~~~~~~~~~~~~~~~~
@@ -13,7 +12,10 @@ To use the MacroSAN drivers, the following are required:
13 12
 
14 13
 - MacroSAN Storage arrays with:
15 14
   - iSCSI or FC host interfaces
16
-  - Enable RESTful service on the MacroSAN Storage Appliance.
15
+  - Enable RESTful service on the MacroSAN Storage Appliance. (The service is
16
+  automatically turned on in the device. You can check if
17
+  `python /odsp/scripts/devop/devop.py` is available via `ps -aux|grep python`.
18
+  )
17 19
 
18 20
 - Network connectivity between the OpenStack host and the array management
19 21
   interfaces
@@ -28,22 +30,13 @@ the ``/etc/cinder/cinder.conf`` file:
28 30
 
29 31
    use_multipath_for_image_xfer = True
30 32
 
31
-Add and change the following configuration keys of
32
-the ``/etc/multipath.conf`` file:
33
+When creating a instance from image, install the ``multipath`` tool and add the
34
+following configuration keys in the ``[libvirt]`` configuration group of
35
+the ``/etc/nova/nova.conf`` file:
33 36
 
34 37
 .. code-block:: ini
35 38
 
36
-    blacklist {
37
-        devnode "^sda$"
38
-        devnode "^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*"
39
-        devnode "^hd[a-z]"
40
-        devnode "^nbd*"
41
-    }
42
-
43
-Need to set user_friendly_names to no in the multipath.conf file.
44
-
45
-In addition, you need to delete the getuid_callout parameter in
46
-the centos7 system.
39
+   iscsi_use_multipath = True
47 40
 
48 41
 Supported operations
49 42
 ~~~~~~~~~~~~~~~~~~~~
@@ -55,13 +48,13 @@ Supported operations
55 48
 - Copy a volume to an image.
56 49
 - Clone a volume.
57 50
 - Extend a volume.
58
-- Volume Migration (Host assisted).
51
+- Volume Migration (Host Assisted).
59 52
 - Volume Migration (Storage Assisted).
60 53
 - Retype a volume.
61 54
 - Manage and unmanage a volume.
62 55
 - Manage and unmanage a snapshot.
63
-- Volume Replication
64
-- Thin Provisioning
56
+- Volume Replication.
57
+- Thin Provisioning.
65 58
 
66 59
 Configuring the array
67 60
 ~~~~~~~~~~~~~~~~~~~~~
@@ -109,7 +102,7 @@ Configuring the array
109 102
       # Name to give this storage back-end.
110 103
       volume_backend_name = macrosan
111 104
 
112
-      #Chose attach/detach volumes in cinder using multipath for volume to image and image to volume transfers.
105
+      #Choose attach/detach volumes in cinder using multipath for volume to image and image to volume transfers.
113 106
       use_multipath_for_image_xfer = True
114 107
 
115 108
       # IP address of the Storage if attaching directly.
@@ -121,7 +114,7 @@ Configuring the array
121 114
       # Storage user password.
122 115
       san_password = openstack
123 116
 
124
-      #Chose using thin-lun or thick lun.When set san_thin_provision to True,you must set
117
+      #Choose using thin-lun or thick lun. When set san_thin_provision to True,you must set
125 118
       #macrosan_thin_lun_extent_size, macrosan_thin_lun_low_watermark, macrosan_thin_lun_high_watermark.
126 119
       san_thin_provision = False
127 120
 
@@ -130,7 +123,7 @@ Configuring the array
130 123
 
131 124
       #The default ports used for initializing connection.
132 125
       #Separate the controller by semicolons (``;``)
133
-      #Separate the ports by semicolons (``,``)
126
+      #Separate the ports by comma (``,``)
134 127
       macrosan_client_default = eth-1:0:0, eth-1:0:1; eth-2:0:0, eth-2:0:1
135 128
 
136 129
       #The switch to force detach volume when deleting
@@ -158,7 +151,7 @@ Configuring the array
158 151
       macrosan_sdas_username = openstack
159 152
       macrosan_sdas_password = openstack
160 153
 
161
-      #The setting of Replication Storage.When you set ip, you must set
154
+      #The setting of Replication Storage. When you set ip, you must set
162 155
       #the macrosan_replication_destination_ports parameter.
163 156
       macrosan_replication_ipaddrs = 172.17.251.142, 172.17.251.143
164 157
       macrosan_replication_username = openstack
@@ -169,7 +162,7 @@ Configuring the array
169 162
       #Separate the ports by semicolons (``/``)
170 163
       macrosan_replication_destination_ports = eth-1:0:0/eth-1:0:1, eth-2:0:0/eth-2:0:1
171 164
 
172
-      #Macrosan iscsi_clients list.You can configure multiple clients.Separate the ports by semicolons (``/``)
165
+      #Macrosan iscsi_clients list. You can configure multiple clients. Separate the ports by semicolons (``/``)
173 166
       macrosan_client = (devstack; controller1name; eth-1:0:0/eth-1:0:1; eth-2:0:0/eth-2:0:1), (dev; controller2name; eth-1:0:0/eth-1:0:1; eth-2:0:0/eth-2:0:1)
174 167
 
175 168
       [cinder-iscsi-b]
@@ -342,7 +335,7 @@ of the MacroSAN volume driver.
342 335
      - iSCSI
343 336
    * - macrosan_client_default
344 337
      - ``None``
345
-     - This is the default connection information for iscsi.This default configuration is used when no host related information is obtained.
338
+     - This is the default connection information for iscsi. This default configuration is used when no host related information is obtained.
346 339
      - iSCSI
347 340
    * - zoning_mode
348 341
      - ``True``
@@ -379,7 +372,7 @@ of the MacroSAN volume driver.
379 372
      - All
380 373
    * - macrosan_replication_ipaddrs
381 374
      - ``-``
382
-     - The ip of replication Storage.When you set ip, you must set
375
+     - The ip of replication Storage. When you set ip, you must set
383 376
        the macrosan_replication_destination_ports parameter.
384 377
      - All
385 378
    * - macrosan_replication_username
@@ -408,7 +401,7 @@ of the MacroSAN volume driver.
408 401
      - All
409 402
    * - macrosan_client
410 403
      - ``True``
411
-     - Macrosan iscsi_clients list.You can configure multiple clients.
404
+     - Macrosan iscsi_clients list. You can configure multiple clients.
412 405
        You can configure it in this format:
413 406
        (hostname; client_name; sp1_iscsi_port; sp2_iscsi_port),
414 407
        E.g:

Loading…
Cancel
Save