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
This commit is contained in:
parent
5259bd7f60
commit
f6343dfc4f
|
@ -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
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue