From f6343dfc4fa460dc2e301e73b66c07f08826cd03 Mon Sep 17 00:00:00 2001 From: zhangni Date: Thu, 4 Dec 2014 16:38:18 +0800 Subject: [PATCH] Implement Huawei SDSHypervisor connector 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 | 120 +++++++++++++ cinder/tests/brick/test_brick_connector.py | 199 +++++++++++++++++++++ 2 files changed, 319 insertions(+) diff --git a/cinder/brick/initiator/connector.py b/cinder/brick/initiator/connector.py index defc65232..2c4d46c69 100644 --- a/cinder/brick/initiator/connector.py +++ b/cinder/brick/initiator/connector.py @@ -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 diff --git a/cinder/tests/brick/test_brick_connector.py b/cinder/tests/brick/test_brick_connector.py index 2fb11db0d..357c51191 100644 --- a/cinder/tests/brick/test_brick_connector.py +++ b/cinder/tests/brick/test_brick_connector.py @@ -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 -- 2.45.2