]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Implement Huawei SDSHypervisor connector
authorzhangni <zhangni@huawei.com>
Thu, 4 Dec 2014 08:38:18 +0000 (16:38 +0800)
committerzhangni <zhangni@huawei.com>
Wed, 17 Dec 2014 03:35:10 +0000 (11:35 +0800)
Huawei SDSHypervisor uses a private key-value data protocal,
so we add a new connector inherited from InitiatorConnector
and implement connect_volume and disconnect_volume.

The connector uses sds_cli cmd to implement attach/detach/querydev vol,
sds_cli will be put to a specific dir and the path is registered as a
system environment variable when sds is installed.

Change-Id: I4fa3306df982347afb5fb8e5d4d14612024b643a
Implements: blueprint huawei-sdshypervisor-driver

cinder/brick/initiator/connector.py
cinder/tests/brick/test_brick_connector.py

index defc652327611568a2de4f4f1211babf64ac0b7f..2c4d46c6974104d8a666cf9523dfe37b1b929db7 100644 (file)
@@ -124,6 +124,13 @@ class InitiatorConnector(executor.Executor):
                                   execute=execute,
                                   device_scan_attempts=device_scan_attempts,
                                   *args, **kwargs)
+        elif protocol == "HUAWEISDSHYPERVISOR":
+            return HuaweiStorHyperConnector(root_helper=root_helper,
+                                            driver=driver,
+                                            execute=execute,
+                                            device_scan_attempts=
+                                            device_scan_attempts,
+                                            *args, **kwargs)
         else:
             msg = (_("Invalid InitiatorConnector protocol "
                      "specified %(protocol)s") %
@@ -928,3 +935,116 @@ class LocalConnector(InitiatorConnector):
     def disconnect_volume(self, connection_properties, device_info):
         """Disconnect a volume from the local host."""
         pass
+
+
+class HuaweiStorHyperConnector(InitiatorConnector):
+    """"Connector class to attach/detach SDSHypervisor volumes."""
+    attached_success_code = 0
+    has_been_attached_code = 50151401
+    attach_mnid_done_code = 50151405
+    vbs_unnormal_code = 50151209
+    not_mount_node_code = 50155007
+    iscliexist = True
+
+    def __init__(self, root_helper, driver=None, execute=putils.execute,
+                 *args, **kwargs):
+        self.cli_path = os.getenv('HUAWEISDSHYPERVISORCLI_PATH')
+        if not self.cli_path:
+            self.cli_path = '/usr/local/bin/sds/sds_cli'
+            LOG.debug("CLI path is not configured, using default %s."
+                      % self.cli_path)
+        if not os.path.isfile(self.cli_path):
+            self.iscliexist = False
+            LOG.error(_LE('SDS CLI file not found, '
+                          'HuaweiStorHyperConnector init failed'))
+        super(HuaweiStorHyperConnector, self).__init__(root_helper,
+                                                       driver=driver,
+                                                       execute=execute,
+                                                       *args, **kwargs)
+
+    @synchronized('connect_volume')
+    def connect_volume(self, connection_properties):
+        """Connect to a volume."""
+        LOG.debug("Connect_volume connection properties: %s."
+                  % connection_properties)
+        out = self._attach_volume(connection_properties['volume_id'])
+        if not out or int(out['ret_code']) not in (self.attached_success_code,
+                                                   self.has_been_attached_code,
+                                                   self.attach_mnid_done_code):
+            msg = (_("Attach volume failed, "
+                   "error code is %s") % out['ret_code'])
+            raise exception.BrickException(msg=msg)
+        out = self._query_attached_volume(
+            connection_properties['volume_id'])
+        if not out or int(out['ret_code']) != 0:
+            msg = _("query attached volume failed or volume not attached.")
+            raise exception.BrickException(msg=msg)
+
+        device_info = {'type': 'block',
+                       'path': out['dev_addr']}
+        return device_info
+
+    @synchronized('connect_volume')
+    def disconnect_volume(self, connection_properties, device_info):
+        """Disconnect a volume from the local host."""
+        LOG.debug("Disconnect_volume: %s." % connection_properties)
+        out = self._detach_volume(connection_properties['volume_id'])
+        if not out or int(out['ret_code']) not in (self.attached_success_code,
+                                                   self.vbs_unnormal_code,
+                                                   self.not_mount_node_code):
+            msg = (_("Disconnect_volume failed, "
+                   "error code is %s") % out['ret_code'])
+            raise exception.BrickException(msg=msg)
+
+    def is_volume_connected(self, volume_name):
+        """Check if volume already connected to host"""
+        LOG.debug('Check if volume %s already connected to a host.'
+                  % volume_name)
+        out = self._query_attached_volume(volume_name)
+        if out:
+            return int(out['ret_code']) == 0
+        return False
+
+    def _attach_volume(self, volume_name):
+        return self._cli_cmd('attach', volume_name)
+
+    def _detach_volume(self, volume_name):
+        return self._cli_cmd('detach', volume_name)
+
+    def _query_attached_volume(self, volume_name):
+        return self._cli_cmd('querydev', volume_name)
+
+    def _cli_cmd(self, method, volume_name):
+        LOG.debug("Enter into _cli_cmd.")
+        if not self.iscliexist:
+            msg = _("SDS command line doesn't exist, "
+                    "cann't execute SDS command.")
+            raise exception.BrickException(msg=msg)
+        if not method or volume_name is None:
+            return
+        cmd = [self.cli_path, '-c', method, '-v', volume_name]
+        out, clilog = self._execute(*cmd, run_as_root=False,
+                                    root_helper=self._root_helper)
+        analyse_result = self._analyze_output(out)
+        LOG.debug('%(method)s volume returns %(analyse_result)s.'
+                  % {'method': method, 'analyse_result': analyse_result})
+        if clilog:
+            LOG.error(_LE("SDS CLI output some log: %s.")
+                      % clilog)
+        return analyse_result
+
+    def _analyze_output(self, out):
+        LOG.debug("Enter into _analyze_output.")
+        if out:
+            analyse_result = {}
+            out_temp = out.split('\n')
+            for line in out_temp:
+                LOG.debug("Line is %s." % line)
+                if line.find('=') != -1:
+                    key, val = line.split('=', 1)
+                    LOG.debug(key + " = " + val)
+                    if key in ['ret_code', 'ret_desc', 'dev_addr']:
+                        analyse_result[key] = val
+            return analyse_result
+        else:
+            return None
index 2fb11db0d5bc21e25343f33b547072b1504fa032..357c511915c09cec5d57cd699f260e47a3ef23bc 100644 (file)
@@ -14,6 +14,7 @@
 
 import os.path
 import string
+import tempfile
 import time
 
 from oslo.concurrency import processutils as putils
@@ -643,3 +644,201 @@ class LocalConnectorTestCase(test.TestCase):
         cprops = {}
         self.assertRaises(ValueError,
                           self.connector.connect_volume, cprops)
+
+
+class HuaweiStorHyperConnectorTestCase(ConnectorTestCase):
+    """Test cases for StorHyper initiator class."""
+
+    attached = False
+
+    def setUp(self):
+        super(HuaweiStorHyperConnectorTestCase, self).setUp()
+        self.fake_sdscli_file = tempfile.mktemp()
+        self.addCleanup(os.remove, self.fake_sdscli_file)
+        newefile = open(self.fake_sdscli_file, 'w')
+        newefile.write('test')
+        newefile.close()
+
+        self.connector = connector.HuaweiStorHyperConnector(
+            None, execute=self.fake_execute)
+        self.connector.cli_path = self.fake_sdscli_file
+        self.connector.iscliexist = True
+
+        self.connector_fail = connector.HuaweiStorHyperConnector(
+            None, execute=self.fake_execute_fail)
+        self.connector_fail.cli_path = self.fake_sdscli_file
+        self.connector_fail.iscliexist = True
+
+        self.connector_nocli = connector.HuaweiStorHyperConnector(
+            None, execute=self.fake_execute_fail)
+        self.connector_nocli.cli_path = self.fake_sdscli_file
+        self.connector_nocli.iscliexist = False
+
+        self.connection_properties = {
+            'access_mode': 'rw',
+            'qos_specs': None,
+            'volume_id': 'volume-b2911673-863c-4380-a5f2-e1729eecfe3f'
+        }
+
+        self.device_info = {'type': 'block',
+                            'path': '/dev/vdxxx'}
+        HuaweiStorHyperConnectorTestCase.attached = False
+
+    def fake_execute(self, *cmd, **kwargs):
+        method = cmd[2]
+        self.cmds.append(string.join(cmd))
+        if 'attach' == method:
+            HuaweiStorHyperConnectorTestCase.attached = True
+            return 'ret_code=0', None
+        if 'querydev' == method:
+            if HuaweiStorHyperConnectorTestCase.attached:
+                return 'ret_code=0\ndev_addr=/dev/vdxxx', None
+            else:
+                return 'ret_code=1\ndev_addr=/dev/vdxxx', None
+        if 'detach' == method:
+            HuaweiStorHyperConnectorTestCase.attached = False
+            return 'ret_code=0', None
+
+    def fake_execute_fail(self, *cmd, **kwargs):
+        method = cmd[2]
+        self.cmds.append(string.join(cmd))
+        if 'attach' == method:
+            HuaweiStorHyperConnectorTestCase.attached = False
+            return 'ret_code=330151401', None
+        if 'querydev' == method:
+            if HuaweiStorHyperConnectorTestCase.attached:
+                return 'ret_code=0\ndev_addr=/dev/vdxxx', None
+            else:
+                return 'ret_code=1\ndev_addr=/dev/vdxxx', None
+        if 'detach' == method:
+            HuaweiStorHyperConnectorTestCase.attached = True
+            return 'ret_code=330155007', None
+
+    def test_connect_volume(self):
+        """Test the basic connect volume case."""
+
+        retval = self.connector.connect_volume(self.connection_properties)
+        self.assertEqual(self.device_info, retval)
+
+        expected_commands = [self.fake_sdscli_file + ' -c attach'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
+                             self.fake_sdscli_file + ' -c querydev'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f']
+        LOG.debug("self.cmds = %s." % self.cmds)
+        LOG.debug("expected = %s." % expected_commands)
+
+        self.assertEqual(expected_commands, self.cmds)
+
+    def test_disconnect_volume(self):
+        """Test the basic disconnect volume case."""
+        self.connector.connect_volume(self.connection_properties)
+        self.assertEqual(True, HuaweiStorHyperConnectorTestCase.attached)
+        self.connector.disconnect_volume(self.connection_properties,
+                                         self.device_info)
+        self.assertEqual(False, HuaweiStorHyperConnectorTestCase.attached)
+
+        expected_commands = [self.fake_sdscli_file + ' -c attach'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
+                             self.fake_sdscli_file + ' -c querydev'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
+                             self.fake_sdscli_file + ' -c detach'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f']
+
+        LOG.debug("self.cmds = %s." % self.cmds)
+        LOG.debug("expected = %s." % expected_commands)
+
+        self.assertEqual(expected_commands, self.cmds)
+
+    def test_is_volume_connected(self):
+        """Test if volume connected to host case."""
+        self.connector.connect_volume(self.connection_properties)
+        self.assertEqual(True, HuaweiStorHyperConnectorTestCase.attached)
+        is_connected = self.connector.is_volume_connected(
+            'volume-b2911673-863c-4380-a5f2-e1729eecfe3f')
+        self.assertEqual(HuaweiStorHyperConnectorTestCase.attached,
+                         is_connected)
+        self.connector.disconnect_volume(self.connection_properties,
+                                         self.device_info)
+        self.assertEqual(False, HuaweiStorHyperConnectorTestCase.attached)
+        is_connected = self.connector.is_volume_connected(
+            'volume-b2911673-863c-4380-a5f2-e1729eecfe3f')
+        self.assertEqual(HuaweiStorHyperConnectorTestCase.attached,
+                         is_connected)
+
+        expected_commands = [self.fake_sdscli_file + ' -c attach'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
+                             self.fake_sdscli_file + ' -c querydev'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
+                             self.fake_sdscli_file + ' -c querydev'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
+                             self.fake_sdscli_file + ' -c detach'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
+                             self.fake_sdscli_file + ' -c querydev'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f']
+
+        LOG.debug("self.cmds = %s." % self.cmds)
+        LOG.debug("expected = %s." % expected_commands)
+
+        self.assertEqual(expected_commands, self.cmds)
+
+    def test__analyze_output(self):
+        cliout = 'ret_code=0\ndev_addr=/dev/vdxxx\nret_desc="success"'
+        analyze_result = {'dev_addr': '/dev/vdxxx',
+                          'ret_desc': '"success"',
+                          'ret_code': '0'}
+        result = self.connector._analyze_output(cliout)
+        self.assertEqual(analyze_result, result)
+
+    def test_connect_volume_fail(self):
+        """Test the fail connect volume case."""
+        self.assertRaises(exception.BrickException,
+                          self.connector_fail.connect_volume,
+                          self.connection_properties)
+        expected_commands = [self.fake_sdscli_file + ' -c attach'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f']
+        LOG.debug("self.cmds = %s." % self.cmds)
+        LOG.debug("expected = %s." % expected_commands)
+        self.assertEqual(expected_commands, self.cmds)
+
+    def test_disconnect_volume_fail(self):
+        """Test the fail disconnect volume case."""
+        self.connector.connect_volume(self.connection_properties)
+        self.assertEqual(True, HuaweiStorHyperConnectorTestCase.attached)
+        self.assertRaises(exception.BrickException,
+                          self.connector_fail.disconnect_volume,
+                          self.connection_properties,
+                          self.device_info)
+
+        expected_commands = [self.fake_sdscli_file + ' -c attach'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
+                             self.fake_sdscli_file + ' -c querydev'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
+                             self.fake_sdscli_file + ' -c detach'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f']
+
+        LOG.debug("self.cmds = %s." % self.cmds)
+        LOG.debug("expected = %s." % expected_commands)
+
+        self.assertEqual(expected_commands, self.cmds)
+
+    def test_connect_volume_nocli(self):
+        """Test the fail connect volume case."""
+        self.assertRaises(exception.BrickException,
+                          self.connector_nocli.connect_volume,
+                          self.connection_properties)
+
+    def test_disconnect_volume_nocli(self):
+        """Test the fail disconnect volume case."""
+        self.connector.connect_volume(self.connection_properties)
+        self.assertEqual(True, HuaweiStorHyperConnectorTestCase.attached)
+        self.assertRaises(exception.BrickException,
+                          self.connector_nocli.disconnect_volume,
+                          self.connection_properties,
+                          self.device_info)
+        expected_commands = [self.fake_sdscli_file + ' -c attach'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f',
+                             self.fake_sdscli_file + ' -c querydev'
+                             ' -v volume-b2911673-863c-4380-a5f2-e1729eecfe3f']
+
+        LOG.debug("self.cmds = %s." % self.cmds)
+        LOG.debug("expected = %s." % expected_commands)
\ No newline at end of file