Merge "Fujitsu Driver: Improve CLI function"
This commit is contained in:
commit
debc28490f
@ -24,6 +24,7 @@ from cinder import exception
|
||||
from cinder import ssh_utils
|
||||
from cinder.tests.unit import test
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.drivers.fujitsu.eternus_dx import constants as CONSTANTS
|
||||
|
||||
with mock.patch.dict('sys.modules', pywbem=mock.Mock()):
|
||||
from cinder.volume.drivers.fujitsu.eternus_dx \
|
||||
@ -35,6 +36,8 @@ with mock.patch.dict('sys.modules', pywbem=mock.Mock()):
|
||||
from cinder.volume.drivers.fujitsu.eternus_dx \
|
||||
import eternus_dx_iscsi as dx_iscsi
|
||||
|
||||
PRIVATE_KEY_PATH = '/etc/cinder/eternus'
|
||||
|
||||
CONFIG_FILE_NAME = 'cinder_fujitsu_eternus_dx.xml'
|
||||
STORAGE_SYSTEM = '172.16.0.2'
|
||||
|
||||
@ -111,7 +114,7 @@ TEST_CONNECTOR = {'initiator': ISCSI_INITIATOR, 'wwpns': TEST_WWPN}
|
||||
|
||||
STORAGE_IP = '172.16.0.2'
|
||||
TEST_USER = 'testuser'
|
||||
TEST_PASSWORD = 'testpassword'
|
||||
TEST_PASSWORD = 'testpass'
|
||||
|
||||
STOR_CONF_SVC = 'FUJITSU_StorageConfigurationService'
|
||||
CTRL_CONF_SVC = 'FUJITSU_ControllerConfigurationService'
|
||||
@ -181,7 +184,7 @@ FAKE_POOLS = [{
|
||||
}]
|
||||
|
||||
FAKE_STATS = {
|
||||
'driver_version': '1.4.7',
|
||||
'driver_version': '1.4.8',
|
||||
'storage_protocol': 'iSCSI',
|
||||
'vendor_name': 'FUJITSU',
|
||||
'QoS_support': True,
|
||||
@ -191,7 +194,7 @@ FAKE_STATS = {
|
||||
'pools': FAKE_POOLS,
|
||||
}
|
||||
FAKE_STATS2 = {
|
||||
'driver_version': '1.4.7',
|
||||
'driver_version': '1.4.8',
|
||||
'storage_protocol': 'FC',
|
||||
'vendor_name': 'FUJITSU',
|
||||
'QoS_support': True,
|
||||
@ -353,7 +356,7 @@ FAKE_MODEL_INFO2 = {
|
||||
|
||||
FAKE_CLI_OUTPUT = {
|
||||
"result": 0,
|
||||
'rc': '0',
|
||||
'rc': str(CONSTANTS.RC_OK),
|
||||
"message": 'TEST_MESSAGE'
|
||||
}
|
||||
|
||||
@ -429,7 +432,7 @@ class FakeEternusConnection(object):
|
||||
global MAP_STAT, VOL_STAT
|
||||
if MethodName == 'CreateOrModifyElementFromStoragePool':
|
||||
VOL_STAT = '1'
|
||||
rc = 0
|
||||
rc = CONSTANTS.RC_OK
|
||||
vol = self._enum_volumes()
|
||||
if InPool.get('InstanceID') == 'FUJITSU:RSP0005':
|
||||
job = {'TheElement': vol[1].path}
|
||||
@ -440,29 +443,29 @@ class FakeEternusConnection(object):
|
||||
job = {'TheElement': vol[0].path}
|
||||
elif MethodName == 'ReturnToStoragePool':
|
||||
VOL_STAT = '0'
|
||||
rc = 0
|
||||
rc = CONSTANTS.RC_OK
|
||||
job = {}
|
||||
elif MethodName == 'GetReplicationRelationships':
|
||||
rc = 0
|
||||
rc = CONSTANTS.RC_OK
|
||||
job = {'Synchronizations': []}
|
||||
elif MethodName == 'ExposePaths':
|
||||
MAP_STAT = '1'
|
||||
rc = 0
|
||||
rc = CONSTANTS.RC_OK
|
||||
job = {}
|
||||
elif MethodName == 'HidePaths':
|
||||
MAP_STAT = '0'
|
||||
rc = 0
|
||||
rc = CONSTANTS.RC_OK
|
||||
job = {}
|
||||
elif MethodName == 'CreateElementReplica':
|
||||
rc = 0
|
||||
rc = CONSTANTS.RC_OK
|
||||
snap = self._enum_snapshots()
|
||||
job = {'TargetElement': snap[0].path}
|
||||
elif MethodName == 'CreateReplica':
|
||||
rc = 0
|
||||
rc = CONSTANTS.RC_OK
|
||||
snap = self._enum_snapshots()
|
||||
job = {'TargetElement': snap[0].path}
|
||||
elif MethodName == 'ModifyReplicaSynchronization':
|
||||
rc = 0
|
||||
rc = CONSTANTS.RC_OK
|
||||
job = {}
|
||||
else:
|
||||
raise exception.VolumeBackendAPIException(data="invoke method")
|
||||
@ -1083,6 +1086,8 @@ class FJFCDriverTestCase(test.TestCase):
|
||||
self.configuration.cinder_eternus_config_file = self.config_file.name
|
||||
self.configuration.safe_get = self.fake_safe_get
|
||||
self.configuration.max_over_subscription_ratio = '20.0'
|
||||
self.configuration.fujitsu_passwordless = False
|
||||
self.configuration.fujitsu_private_key_path = PRIVATE_KEY_PATH
|
||||
self.configuration.fujitsu_use_cli_copy = False
|
||||
|
||||
self.mock_object(dx_common.FJDXCommon, '_get_eternus_connection',
|
||||
@ -1398,6 +1403,8 @@ class FJISCSIDriverTestCase(test.TestCase):
|
||||
self.configuration.cinder_eternus_config_file = self.config_file.name
|
||||
self.configuration.safe_get = self.fake_safe_get
|
||||
self.configuration.max_over_subscription_ratio = '20.0'
|
||||
self.configuration.fujitsu_passwordless = False
|
||||
self.configuration.fujitsu_private_key_path = PRIVATE_KEY_PATH
|
||||
self.configuration.fujitsu_use_cli_copy = False
|
||||
|
||||
self.mock_object(dx_common.FJDXCommon, '_get_eternus_connection',
|
||||
@ -1781,31 +1788,28 @@ class FJCLITestCase(test.TestCase):
|
||||
ret = '%s\r\n00\r\n0019\r\nCLI> ' % exec_cmdline
|
||||
elif exec_cmdline.startswith('start copy-opc'):
|
||||
ret = '%s\r\n00\r\n0019\r\nCLI> ' % exec_cmdline
|
||||
elif exec_cmdline.startswith('show cli-error-code'):
|
||||
ret = '%s\r\n00\r\n0001\r\n0001\tBad Value\r\nCLI> ' % exec_cmdline
|
||||
else:
|
||||
ret = None
|
||||
return ret
|
||||
|
||||
@mock.patch.object(eternus_dx_cli.FJDXCLI, '_exec_cli_with_eternus')
|
||||
def test_create_error_message(self, mock_exec_cli_with_eternus):
|
||||
expected_error_value = {'message': ['-bandwidth-limit', 'asdf'],
|
||||
'rc': 'E8101',
|
||||
'result': 0}
|
||||
def test_show_cli_error_message(self):
|
||||
FAKE_OPTION = {'error-code': '0001'}
|
||||
FAKE_MESSAGE = 'Bad Value'
|
||||
FAKE_MESSAGE_OUTPUT = {**FAKE_CLI_OUTPUT, 'message': FAKE_MESSAGE}
|
||||
|
||||
FAKE_VOLUME_NAME = 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||
FAKE_BANDWIDTH_LIMIT = 'abcd'
|
||||
FAKE_QOS_OPTION = self.create_fake_options(
|
||||
volume_name=FAKE_VOLUME_NAME,
|
||||
bandwidth_limit=FAKE_BANDWIDTH_LIMIT)
|
||||
ERROR_MESSAGE_OUTPUT = self.cli._show_cli_error_message(**FAKE_OPTION)
|
||||
self.assertEqual(FAKE_MESSAGE_OUTPUT, ERROR_MESSAGE_OUTPUT)
|
||||
|
||||
error_cli_output = ('\r\nCLI> set volume-qos -volume-name %s '
|
||||
'-bandwidth-limit %s\r\n'
|
||||
'01\r\n8101\r\n-bandwidth-limit\r\nasdf\r\n'
|
||||
'CLI> ' % (FAKE_VOLUME_NAME, FAKE_BANDWIDTH_LIMIT))
|
||||
mock_exec_cli_with_eternus.return_value = error_cli_output
|
||||
def test_create_error_message(self):
|
||||
FAKE_CODE = '0001'
|
||||
FAKE_MSG = 'Bad Value'
|
||||
expected_error_message = ('E' + FAKE_CODE, FAKE_MSG)
|
||||
|
||||
error_qos_output = self.cli._set_volume_qos(**FAKE_QOS_OPTION)
|
||||
|
||||
self.assertEqual(expected_error_value, error_qos_output)
|
||||
ERROR_MESSAGE = self.cli._create_error_message(FAKE_CODE,
|
||||
FAKE_MSG)
|
||||
self.assertEqual(expected_error_message, ERROR_MESSAGE)
|
||||
|
||||
def test_get_options(self):
|
||||
expected_option = " -bandwidth-limit 2"
|
||||
@ -2005,6 +2009,8 @@ class FJCommonTestCase(test.TestCase):
|
||||
self.configuration.cinder_eternus_config_file = self.config_file.name
|
||||
self.configuration.safe_get = self.fake_safe_get
|
||||
self.configuration.max_over_subscription_ratio = '20.0'
|
||||
self.configuration.fujitsu_passwordless = False
|
||||
self.configuration.fujitsu_private_key_path = PRIVATE_KEY_PATH
|
||||
self.configuration.fujitsu_use_cli_copy = False
|
||||
|
||||
self.mock_object(dx_common.FJDXCommon, '_get_eternus_connection',
|
||||
@ -2107,6 +2113,101 @@ class FJCommonTestCase(test.TestCase):
|
||||
for key, value in diction.items():
|
||||
volume[key] = value
|
||||
|
||||
@mock.patch.object(ssh_utils, 'SSHPool')
|
||||
def test_ssh_to_storage_by_password(self, mock_ssh_pool):
|
||||
command = 'show_enclosure_status'
|
||||
self.driver.common.fjdxcli = {}
|
||||
self.driver.common._exec_eternus_cli(command)
|
||||
|
||||
mock_ssh_pool.assert_called_with(STORAGE_IP, 22, None, TEST_USER,
|
||||
password=TEST_PASSWORD, max_size=2)
|
||||
|
||||
@mock.patch.object(ssh_utils, 'SSHPool')
|
||||
def test_ssh_to_storage_by_key(self, mock_ssh_pool):
|
||||
command = 'show_enclosure_status'
|
||||
self.configuration.fujitsu_passwordless = True
|
||||
driver = dx_iscsi.FJDXISCSIDriver(configuration=self.configuration)
|
||||
self.driver = driver
|
||||
|
||||
self.driver.common.fjdxcli = {}
|
||||
self.driver.common._exec_eternus_cli(command)
|
||||
|
||||
mock_ssh_pool.assert_called_with(STORAGE_IP, 22, None, TEST_USER,
|
||||
privatekey=PRIVATE_KEY_PATH,
|
||||
max_size=2)
|
||||
|
||||
def test_exec_eternus_cli_success(self):
|
||||
command = 'show_enclosure_status'
|
||||
|
||||
FAKE_CLI_ENCLOUSER_STATUS = (0, None, {'version': 'V10L87-9000'})
|
||||
|
||||
cli_enclosure_status = self.driver.common._exec_eternus_cli(command)
|
||||
|
||||
self.assertEqual(FAKE_CLI_ENCLOUSER_STATUS, cli_enclosure_status)
|
||||
|
||||
@mock.patch.object(eternus_dx_cli.FJDXCLI, '_exec_cli_with_eternus')
|
||||
def test_exec_eternus_cli_success_with_retry(self,
|
||||
mock_exec_cli_with_eternus):
|
||||
command = 'stop_copy_session'
|
||||
mock_exec_cli_with_eternus.side_effect = [
|
||||
'\r\nCLI> stop copy-session\r\n01\r\n0060\r\nCLI> ',
|
||||
'\r\nCLI> show cli-error-code -error-code '
|
||||
'0060\r\n00\r\n0060\r\n0060\tResource locked\r\nCLI> ',
|
||||
'\r\nCLI> stop copy-session\r\n00\r\nCLI> ']
|
||||
|
||||
retry_msg = 'INFO:cinder.volume.drivers.fujitsu.eternus_dx.' \
|
||||
'eternus_dx_common:_exec_eternus_cli, retry, ' \
|
||||
'ip: 172.16.0.2, RetryCode: E0060, TryNum: 1.'
|
||||
FAKE_STOP_COPY_SESSION = (0, None, [])
|
||||
|
||||
with self.assertLogs('cinder.volume.drivers.fujitsu.eternus_dx.'
|
||||
'eternus_dx_common', level='INFO') as cm:
|
||||
cli_return = self.driver.common._exec_eternus_cli(command)
|
||||
|
||||
self.assertIn(retry_msg, cm.output)
|
||||
self.assertEqual(FAKE_STOP_COPY_SESSION, cli_return)
|
||||
|
||||
@mock.patch.object(eternus_dx_cli.FJDXCLI, '_exec_cli_with_eternus')
|
||||
def test_exec_eternus_cli_authentication_fail(self,
|
||||
mock_exec_cli_with_eternus):
|
||||
command = 'check_user_role'
|
||||
mock_exec_cli_with_eternus.side_effect = (
|
||||
exception.VolumeBackendAPIException(
|
||||
'Execute CLI command error. Error: Authentication failed.'))
|
||||
authentication_fail_msg = 'WARNING:' \
|
||||
'cinder.volume.drivers.fujitsu.eternus_dx.' \
|
||||
'eternus_dx_common:_exec_eternus_cli, ' \
|
||||
'retry, ip: 172.16.0.2, ' \
|
||||
'Message: Execute CLI command error. ' \
|
||||
'Error: Authentication failed., TryNum: 1.'
|
||||
FAKE_STOP_COPY_SESSION = (4, '4',
|
||||
'Execute CLI command error. '
|
||||
'Error: Authentication failed.')
|
||||
|
||||
with self.assertLogs('cinder.volume.drivers.fujitsu.eternus_dx.'
|
||||
'eternus_dx_common', level='WARNING') as cm:
|
||||
cli_return = self.driver.common._exec_eternus_cli(command)
|
||||
self.assertIn(authentication_fail_msg, cm.output)
|
||||
self.assertEqual(FAKE_STOP_COPY_SESSION, cli_return)
|
||||
|
||||
@mock.patch.object(eternus_dx_cli.FJDXCLI, '_exec_cli_with_eternus')
|
||||
@mock.patch.object(dx_common, 'LOG')
|
||||
def test_exec_eternus_cli_retry_exceed(self, mock_log,
|
||||
mock_exec_cli_with_eternus):
|
||||
command = 'stop_copy_session'
|
||||
mock_exec_cli_with_eternus.side_effect = [
|
||||
'\r\nCLI> stop copy-session\r\n01\r\n0060\r\nCLI> ',
|
||||
'\r\nCLI> show cli-error-code -error-code '
|
||||
'0060\r\n00\r\n0060\r\n0060\tResource locked\r\nCLI> '] * 3
|
||||
|
||||
exceed_msg = '_exec_eternus_cli, Retry was exceeded.'
|
||||
FAKE_STOP_COPY_SESSION = (4, 'E0060', 'Resource locked')
|
||||
|
||||
cli_return = self.driver.common._exec_eternus_cli(command)
|
||||
|
||||
mock_log.warning.assert_called_with(exceed_msg)
|
||||
self.assertEqual(FAKE_STOP_COPY_SESSION, cli_return)
|
||||
|
||||
def test_get_eternus_model(self):
|
||||
ETERNUS_MODEL = self.driver.common._get_eternus_model()
|
||||
self.assertEqual(3, ETERNUS_MODEL)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,7 @@
|
||||
|
||||
from cinder.i18n import _
|
||||
from cinder import ssh_utils
|
||||
from cinder.volume.drivers.fujitsu.eternus_dx import constants as CONSTANTS
|
||||
|
||||
|
||||
class FJDXCLI(object):
|
||||
@ -59,12 +60,6 @@ class FJDXCLI(object):
|
||||
'delete_volume': self._delete_volume
|
||||
}
|
||||
|
||||
self.SMIS_dic = {
|
||||
'0000': '0', # Success.
|
||||
'0060': '32787', # The device is in busy state.
|
||||
'0100': '4097'
|
||||
} # Size not supported.
|
||||
|
||||
def done(self, command, **option):
|
||||
func = self.CMD_dic.get(command, self._default_func)
|
||||
return func(**option)
|
||||
@ -105,7 +100,7 @@ class FJDXCLI(object):
|
||||
'stdoutlist': stdoutlist})
|
||||
|
||||
if status == 0:
|
||||
rc = '0'
|
||||
rc = str(CONSTANTS.RC_OK)
|
||||
for outline in stdoutlist[lineno:]:
|
||||
if 0 <= outline.find('CLI>'):
|
||||
continue
|
||||
@ -124,8 +119,11 @@ class FJDXCLI(object):
|
||||
if outline is None:
|
||||
continue
|
||||
output.append(outline)
|
||||
|
||||
rc, message = self._create_error_message(code, output)
|
||||
if cmd != "show cli-error-code":
|
||||
rc, message = self._create_error_message(code, output)
|
||||
else:
|
||||
rc = 'E' + code
|
||||
message = output
|
||||
|
||||
return {'result': 0, 'rc': rc, 'message': message}
|
||||
|
||||
@ -160,22 +158,29 @@ class FJDXCLI(object):
|
||||
self.ssh_pool.remove(ssh)
|
||||
return stdoutdata
|
||||
|
||||
def _show_cli_error_message(self, **option):
|
||||
"""Get error messages by error code."""
|
||||
output = self._exec_cli("show cli-error-code",
|
||||
**option)
|
||||
rc = output['rc']
|
||||
if rc != str(CONSTANTS.RC_OK):
|
||||
raise Exception(_('_show_cli_error_message failed. '
|
||||
'Return code: %lu') % rc)
|
||||
message = output['message'][1]
|
||||
output['message'] = message.split('\t')[1]
|
||||
return output
|
||||
|
||||
def _create_error_message(self, code, msg):
|
||||
"""Create error code and message using arguements."""
|
||||
message = None
|
||||
if code in self.SMIS_dic:
|
||||
rc = self.SMIS_dic[code]
|
||||
else:
|
||||
rc = 'E' + code
|
||||
|
||||
# TODO(whfnst): we will have a dic to store errors.
|
||||
if rc == "E0001":
|
||||
message = "Bad value: %s" % msg
|
||||
elif rc == "ED184":
|
||||
message = "Because OPC is being executed, "
|
||||
"the processing was discontinued."
|
||||
else:
|
||||
message = msg
|
||||
rc = 'E' + code
|
||||
try:
|
||||
option = {
|
||||
'error-code': code
|
||||
}
|
||||
message = self._show_cli_error_message(**option)['message']
|
||||
except Exception:
|
||||
message = CONSTANTS.CLIRETCODE_dic.get(rc, msg)
|
||||
|
||||
return rc, message
|
||||
|
||||
@ -214,7 +219,7 @@ class FJDXCLI(object):
|
||||
**option)
|
||||
# Return error.
|
||||
rc = output['rc']
|
||||
if rc != "0":
|
||||
if rc != str(CONSTANTS.RC_OK):
|
||||
return output
|
||||
|
||||
userlist = output.get('message')
|
||||
@ -237,7 +242,7 @@ class FJDXCLI(object):
|
||||
msg = str(ex)
|
||||
output = {
|
||||
'result': 0,
|
||||
'rc': '4',
|
||||
'rc': str(CONSTANTS.RC_FAILED),
|
||||
'message': msg
|
||||
}
|
||||
return output
|
||||
@ -257,7 +262,7 @@ class FJDXCLI(object):
|
||||
|
||||
rc = output['rc']
|
||||
|
||||
if rc != "0":
|
||||
if rc != str(CONSTANTS.RC_OK):
|
||||
return output
|
||||
|
||||
clidatalist = output.get('message')
|
||||
@ -274,7 +279,7 @@ class FJDXCLI(object):
|
||||
except Exception as ex:
|
||||
output = {
|
||||
'result': 0,
|
||||
'rc': '4',
|
||||
'rc': str(CONSTANTS.RC_FAILED),
|
||||
'message': "show pool provision capacity error: %s" % ex
|
||||
}
|
||||
|
||||
@ -288,7 +293,7 @@ class FJDXCLI(object):
|
||||
# return error
|
||||
rc = output['rc']
|
||||
|
||||
if rc != "0":
|
||||
if rc != str(CONSTANTS.RC_OK):
|
||||
return output
|
||||
|
||||
cpsdatalist = []
|
||||
@ -360,7 +365,7 @@ class FJDXCLI(object):
|
||||
output['message'] = cpsdatalist
|
||||
except Exception as ex:
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'rc': str(CONSTANTS.RC_FAILED),
|
||||
'message': "Show copy sessions error: %s"
|
||||
% str(ex)}
|
||||
|
||||
@ -375,7 +380,7 @@ class FJDXCLI(object):
|
||||
# return error
|
||||
rc = output['rc']
|
||||
|
||||
if rc != "0":
|
||||
if rc != str(CONSTANTS.RC_OK):
|
||||
return output
|
||||
|
||||
qoslist = []
|
||||
@ -399,13 +404,13 @@ class FJDXCLI(object):
|
||||
msg = ('The results returned by cli are not as expected. '
|
||||
'Exception string: %s' % clidata)
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'rc': str(CONSTANTS.RC_FAILED),
|
||||
'message': "Show qos bandwidth limit error: %s. %s"
|
||||
% (ex, msg)}
|
||||
|
||||
except Exception as ex:
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'rc': str(CONSTANTS.RC_FAILED),
|
||||
'message': "Show qos bandwidth limit error: %s" % ex}
|
||||
|
||||
return output
|
||||
@ -423,7 +428,7 @@ class FJDXCLI(object):
|
||||
# return error
|
||||
rc = output['rc']
|
||||
|
||||
if rc != "0":
|
||||
if rc != str(CONSTANTS.RC_OK):
|
||||
return output
|
||||
|
||||
vqosdatalist = []
|
||||
@ -441,12 +446,12 @@ class FJDXCLI(object):
|
||||
msg = ('The results returned by cli are not as expected. '
|
||||
'Exception string: %s' % clidata)
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'rc': str(CONSTANTS.RC_FAILED),
|
||||
'message': "Show volume qos error: %s. %s" % (ex, msg)}
|
||||
|
||||
except Exception as ex:
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'rc': str(CONSTANTS.RC_FAILED),
|
||||
'message': "Show volume qos error: %s" % ex}
|
||||
|
||||
return output
|
||||
@ -460,7 +465,7 @@ class FJDXCLI(object):
|
||||
# return error
|
||||
rc = output['rc']
|
||||
|
||||
if rc != "0":
|
||||
if rc != str(CONSTANTS.RC_OK):
|
||||
return output
|
||||
|
||||
clidatalist = output.get('message')
|
||||
@ -473,13 +478,13 @@ class FJDXCLI(object):
|
||||
msg = ('The results returned by cli are not as expected. '
|
||||
'Exception string: %s' % clidata)
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'rc': str(CONSTANTS.RC_FAILED),
|
||||
'message': "Show enclosure status error: %s. %s"
|
||||
% (ex, msg)}
|
||||
|
||||
except Exception as ex:
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'rc': str(CONSTANTS.RC_FAILED),
|
||||
'message': "Show enclosure status error: %s" % ex}
|
||||
|
||||
return output
|
||||
|
@ -53,6 +53,14 @@ FJ_ETERNUS_DX_OPT_opts = [
|
||||
cfg.StrOpt('cinder_eternus_config_file',
|
||||
default='/etc/cinder/cinder_fujitsu_eternus_dx.xml',
|
||||
help='Config file for cinder eternus_dx volume driver.'),
|
||||
cfg.BoolOpt('fujitsu_passwordless',
|
||||
default=True,
|
||||
help='Use SSH key to connect to storage.'),
|
||||
cfg.StrOpt('fujitsu_private_key_path',
|
||||
default='$state_path/eternus',
|
||||
help='Filename of private key for ETERNUS CLI. '
|
||||
'This option must be set when '
|
||||
'the fujitsu_passwordless is True.'),
|
||||
cfg.BoolOpt('fujitsu_use_cli_copy',
|
||||
default=False,
|
||||
help='If True use CLI command to create snapshot.'),
|
||||
@ -76,10 +84,12 @@ class FJDXCommon(object):
|
||||
1.4.5 - Add metadata for snapshot.
|
||||
1.4.6 - Add parameter fujitsu_use_cli_copy.
|
||||
1.4.7 - Add support for revert-to-snapshot.
|
||||
1.4.8 - Improve the processing flow of CLI error messages.(bug #2048850)
|
||||
- Add support connect to storage using SSH key.
|
||||
|
||||
"""
|
||||
|
||||
VERSION = "1.4.7"
|
||||
VERSION = "1.4.8"
|
||||
stats = {
|
||||
'driver_version': VERSION,
|
||||
'storage_protocol': None,
|
||||
@ -97,6 +107,8 @@ class FJDXCommon(object):
|
||||
self.configuration.append_config_values(FJ_ETERNUS_DX_OPT_opts)
|
||||
|
||||
self.conn = None
|
||||
self.passwordless = self.configuration.fujitsu_passwordless
|
||||
self.private_key_path = self.configuration.fujitsu_private_key_path
|
||||
self.use_cli_copy = self.configuration.fujitsu_use_cli_copy
|
||||
self.fjdxcli = {}
|
||||
self.model_name = self._get_eternus_model()
|
||||
@ -222,7 +234,7 @@ class FJDXCommon(object):
|
||||
'Element Name is in use.',
|
||||
{'volumename': volumename})
|
||||
element = self._find_lun(volume)
|
||||
elif rc != 0:
|
||||
elif rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_create_volume, '
|
||||
'volumename: %(volumename)s, '
|
||||
'poolname: %(eternus_pool)s, '
|
||||
@ -440,7 +452,7 @@ class FJDXCommon(object):
|
||||
SourceElement=src_vol_instance.path,
|
||||
TargetElement=tgt_vol_instance.path)
|
||||
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_create_local_cloned_volume, '
|
||||
'volumename: %(volumename)s, '
|
||||
'sourcevolumename: %(sourcevolumename)s, '
|
||||
@ -546,7 +558,7 @@ class FJDXCommon(object):
|
||||
'stop_copy_session',
|
||||
**param_dict)
|
||||
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_delete_volume_setting, '
|
||||
'stop_copy_session failed. '
|
||||
'Return code: %(rc)lu, '
|
||||
@ -601,7 +613,7 @@ class FJDXCommon(object):
|
||||
configservice,
|
||||
TheElement=vol_instance.path)
|
||||
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_delete_volume, volumename: %(volumename)s, '
|
||||
'Return code: %(rc)lu, '
|
||||
'Error: %(errordesc)s.')
|
||||
@ -630,7 +642,7 @@ class FJDXCommon(object):
|
||||
'delete_volume',
|
||||
**param_dict)
|
||||
|
||||
if rc == 0:
|
||||
if rc == CONSTANTS.RC_OK:
|
||||
msg = (_('_delete_volume_after_error, '
|
||||
'volumename: %(volumename)s, '
|
||||
'Delete Successed.')
|
||||
@ -774,7 +786,7 @@ class FJDXCommon(object):
|
||||
smis_method, smis_service,
|
||||
**params)
|
||||
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
LOG.warning('_create_snapshot, '
|
||||
'snapshotname: %(snapshotname)s, '
|
||||
'source volume name: %(volumename)s, '
|
||||
@ -826,14 +838,14 @@ class FJDXCommon(object):
|
||||
ElementType=self._pywbem_uint(pooltype, '16'),
|
||||
Size=self._pywbem_uint(vol_size, '64'))
|
||||
|
||||
if rc == 32769:
|
||||
if rc == CONSTANTS.RG_VOLNUM_MAX:
|
||||
LOG.warning('_create_snapshot, RAID Group pool: %s. '
|
||||
'Maximum number of Logical Volume in a '
|
||||
'RAID Group has been reached. '
|
||||
'Try other pool.',
|
||||
pool)
|
||||
continue
|
||||
elif rc != 0:
|
||||
elif rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_create_volume, '
|
||||
'volumename: %(volumename)s, '
|
||||
'poolname: %(eternus_pool)s, '
|
||||
@ -862,7 +874,7 @@ class FJDXCommon(object):
|
||||
'start_copy_snap_opc',
|
||||
**param_dict)
|
||||
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_create_snapshot, '
|
||||
'create_volume failed. '
|
||||
'Return code: %(rc)lu, '
|
||||
@ -1079,7 +1091,7 @@ class FJDXCommon(object):
|
||||
'expand_volume',
|
||||
**param_dict)
|
||||
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('extend_volume, '
|
||||
'volumename: %(volumename)s, '
|
||||
'Return code: %(rc)lu, '
|
||||
@ -1133,7 +1145,7 @@ class FJDXCommon(object):
|
||||
Size=self._pywbem_uint(volumesize, '64'),
|
||||
TheElement=volume_instance.path)
|
||||
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('extend_volume, '
|
||||
'volumename: %(volumename)s, '
|
||||
'Return code: %(rc)lu, '
|
||||
@ -1543,7 +1555,7 @@ class FJDXCommon(object):
|
||||
'show_pool_provision',
|
||||
**param_dict)
|
||||
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_find_pools, show_pool_provision, '
|
||||
'pool name: %(pool_name)s, '
|
||||
'Return code: %(rc)lu, '
|
||||
@ -2047,7 +2059,7 @@ class FJDXCommon(object):
|
||||
% {'cpsession': cpsession,
|
||||
'operation': operation})
|
||||
raise exception.VolumeIsBusy(msg)
|
||||
elif rc != 0:
|
||||
elif rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_delete_copysession, '
|
||||
'copysession: %(cpsession)s, '
|
||||
'operation: %(operation)s, '
|
||||
@ -2188,7 +2200,7 @@ class FJDXCommon(object):
|
||||
{'errordesc': errordesc,
|
||||
'rc': rc})
|
||||
|
||||
if rc != 0 and rc != CONSTANTS.LUNAME_IN_USE:
|
||||
if rc != CONSTANTS.RC_OK and rc != CONSTANTS.LUNAME_IN_USE:
|
||||
LOG.warning('_map_lun, '
|
||||
'lun_name: %(volume_uid)s, '
|
||||
'Initiator: %(initiator)s, '
|
||||
@ -2221,7 +2233,7 @@ class FJDXCommon(object):
|
||||
{'errordesc': errordesc,
|
||||
'rc': rc})
|
||||
|
||||
if rc != 0 and rc != CONSTANTS.LUNAME_IN_USE:
|
||||
if rc != CONSTANTS.RC_OK and rc != CONSTANTS.LUNAME_IN_USE:
|
||||
LOG.warning('_map_lun, '
|
||||
'lun_name: %(volume_uid)s, '
|
||||
'Initiator: %(initiator)s, '
|
||||
@ -2428,7 +2440,7 @@ class FJDXCommon(object):
|
||||
'volumename: %(volumename)s, '
|
||||
'Invalid LUNames.',
|
||||
{'volumename': volumename})
|
||||
elif rc != 0:
|
||||
elif rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_unmap_lun, '
|
||||
'volumename: %(volumename)s, '
|
||||
'volume_uid: %(volume_uid)s, '
|
||||
@ -2847,7 +2859,7 @@ class FJDXCommon(object):
|
||||
"""Check whether user's role is accessible to ETERNUS and Software."""
|
||||
ret = True
|
||||
rc, errordesc, job = self._exec_eternus_cli('check_user_role')
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_check_user, '
|
||||
'Return code: %(rc)lu, '
|
||||
'Error: %(errordesc)s, '
|
||||
@ -2871,100 +2883,79 @@ class FJDXCommon(object):
|
||||
|
||||
def _exec_eternus_cli(self, command, retry=CONSTANTS.TIMES_MIN,
|
||||
retry_interval=CONSTANTS.RETRY_INTERVAL,
|
||||
retry_code=[32787], filename=None, timeout=None,
|
||||
retry_code=['E0060'], filename=None,
|
||||
**param_dict):
|
||||
"""Execute ETERNUS CLI."""
|
||||
LOG.debug('_exec_eternus_cli, '
|
||||
'command: %(a)s, '
|
||||
'filename: %(f)s, '
|
||||
'timeout: %(t)s, '
|
||||
'parameters: %(b)s.',
|
||||
{'a': command,
|
||||
'f': filename,
|
||||
't': timeout,
|
||||
'b': param_dict})
|
||||
|
||||
result = None
|
||||
out = None
|
||||
rc = None
|
||||
retdata = None
|
||||
errordesc = None
|
||||
filename = self.configuration.cinder_eternus_config_file
|
||||
storage_ip = self._get_drvcfg('EternusIP')
|
||||
storage_ip = self._get_drvcfg('EternusIP', filename)
|
||||
if not self.fjdxcli.get(filename):
|
||||
user = self._get_drvcfg('EternusUser')
|
||||
password = self._get_drvcfg('EternusPassword')
|
||||
self.fjdxcli[filename] = (
|
||||
eternus_dx_cli.FJDXCLI(user, storage_ip,
|
||||
password=password))
|
||||
user = self._get_drvcfg('EternusUser', filename)
|
||||
if self.passwordless:
|
||||
self.fjdxcli[filename] = (
|
||||
eternus_dx_cli.FJDXCLI(user,
|
||||
storage_ip,
|
||||
keyfile=self.private_key_path))
|
||||
else:
|
||||
password = self._get_drvcfg('EternusPassword', filename)
|
||||
self.fjdxcli[filename] = (
|
||||
eternus_dx_cli.FJDXCLI(user, storage_ip,
|
||||
password=password))
|
||||
|
||||
for retry_num in range(retry):
|
||||
# Execute ETERNUS CLI and get return value.
|
||||
try:
|
||||
out_dict = self.fjdxcli[filename].done(command, **param_dict)
|
||||
result = out_dict.get('result')
|
||||
out = self.fjdxcli[filename].done(command, **param_dict)
|
||||
out_dict = out
|
||||
rc_str = out_dict.get('rc')
|
||||
retdata = out_dict.get('message')
|
||||
except Exception as ex:
|
||||
msg = (_('_exec_eternus_cli, '
|
||||
'stdout: %(out)s, '
|
||||
'unexpected error: %(ex)s.')
|
||||
% {'ex': ex})
|
||||
% {'out': out,
|
||||
'ex': ex})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
# Check ssh result.
|
||||
if result == 255:
|
||||
LOG.info('_exec_eternus_cli, retry, '
|
||||
'command: %(command)s, '
|
||||
'option: %(option)s, '
|
||||
'ip: %(ip)s, '
|
||||
'SSH Result: %(result)s, '
|
||||
'retdata: %(retdata)s, '
|
||||
'TryNum: %(rn)s.',
|
||||
{'command': command,
|
||||
'option': param_dict,
|
||||
'ip': storage_ip,
|
||||
'result': result,
|
||||
'retdata': retdata,
|
||||
'rn': (retry_num + 1)})
|
||||
time.sleep(retry_interval)
|
||||
continue
|
||||
elif result != 0:
|
||||
msg = (_('_exec_eternus_cli, '
|
||||
'unexpected error, '
|
||||
'command: %(command)s, '
|
||||
'option: %(option)s, '
|
||||
'ip: %(ip)s, '
|
||||
'resuslt: %(result)s, '
|
||||
'retdata: %(retdata)s.')
|
||||
% {'command': command,
|
||||
'option': param_dict,
|
||||
'ip': storage_ip,
|
||||
'result': result,
|
||||
'retdata': retdata})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
# Check CLI return code.
|
||||
if rc_str.isdigit():
|
||||
# SMI-S style return code.
|
||||
rc = int(rc_str)
|
||||
|
||||
try:
|
||||
errordesc = CONSTANTS.RETCODE_dic[str(rc)]
|
||||
except Exception:
|
||||
errordesc = 'Undefined Error!!'
|
||||
|
||||
if rc in retry_code:
|
||||
if rc_str.startswith('E'):
|
||||
errordesc = rc_str
|
||||
rc = CONSTANTS.RC_FAILED
|
||||
if rc_str in retry_code:
|
||||
LOG.info('_exec_eternus_cli, retry, '
|
||||
'ip: %(ip)s, '
|
||||
'RetryCode: %(rc)s, '
|
||||
'TryNum: %(rn)s.',
|
||||
{'ip': storage_ip,
|
||||
'rc': rc,
|
||||
'rc': rc_str,
|
||||
'rn': (retry_num + 1)})
|
||||
time.sleep(retry_interval)
|
||||
continue
|
||||
if rc == 4:
|
||||
else:
|
||||
LOG.warning('_exec_eternus_cli, '
|
||||
'WARNING!! '
|
||||
'ip: %(ip)s, '
|
||||
'ReturnCode: %(rc_str)s, '
|
||||
'ReturnData: %(retdata)s.',
|
||||
{'ip': storage_ip,
|
||||
'rc_str': rc_str,
|
||||
'retdata': retdata})
|
||||
break
|
||||
else:
|
||||
if rc_str == str(CONSTANTS.RC_FAILED):
|
||||
errordesc = rc_str
|
||||
rc = CONSTANTS.RC_FAILED
|
||||
if ('Authentication failed' in retdata and
|
||||
retry_num + 1 < retry):
|
||||
LOG.warning('_exec_eternus_cli, retry, ip: %(ip)s, '
|
||||
@ -2975,35 +2966,12 @@ class FJDXCommon(object):
|
||||
'rn': (retry_num + 1)})
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
break
|
||||
else:
|
||||
# CLI style return code.
|
||||
LOG.warning('_exec_eternus_cli, '
|
||||
'WARNING!! '
|
||||
'ip: %(ip)s, '
|
||||
'ReturnCode: %(rc_str)s, '
|
||||
'ReturnData: %(retdata)s.',
|
||||
{'ip': storage_ip,
|
||||
'rc_str': rc_str,
|
||||
'retdata': retdata})
|
||||
|
||||
errordesc = rc_str
|
||||
rc = 4 # Failed.
|
||||
else:
|
||||
errordesc = None
|
||||
rc = CONSTANTS.RC_OK
|
||||
break
|
||||
else:
|
||||
if 0 < result:
|
||||
msg = (_('_exec_eternus_cli, '
|
||||
'cannot connect to ETERNUS. '
|
||||
'SSH Result: %(result)s, '
|
||||
'retdata: %(retdata)s.')
|
||||
% {'result': result,
|
||||
'retdata': retdata})
|
||||
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
else:
|
||||
LOG.warning('_exec_eternus_cli, Retry was exceeded.')
|
||||
LOG.warning('_exec_eternus_cli, Retry was exceeded.')
|
||||
|
||||
ret = (rc, errordesc, retdata)
|
||||
|
||||
@ -3062,7 +3030,7 @@ class FJDXCommon(object):
|
||||
|
||||
# Get storage version information.
|
||||
rc, emsg, clidata = self._exec_eternus_cli('show_enclosure_status')
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_set_qos, '
|
||||
'show_enclosure_status failed. '
|
||||
'Return code: %(rc)lu, '
|
||||
@ -3115,7 +3083,7 @@ class FJDXCommon(object):
|
||||
rc, errordesc, job = self._exec_eternus_cli(
|
||||
'set_volume_qos',
|
||||
**category_dict)
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_set_qos, '
|
||||
'set_volume_qos failed. '
|
||||
'Return code: %(rc)lu, '
|
||||
@ -3302,7 +3270,7 @@ class FJDXCommon(object):
|
||||
# Get all the bandwidth limits.
|
||||
rc, errordesc, bandwidthlist = self._exec_eternus_cli(
|
||||
'show_qos_bandwidth_limit')
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_get_qos_category, '
|
||||
'show_qos_bandwidth_limit failed. '
|
||||
'Return code: %(rc)lu, '
|
||||
@ -3335,7 +3303,7 @@ class FJDXCommon(object):
|
||||
return ret_dict
|
||||
|
||||
rc, errordesc, vqosdatalist = self._exec_eternus_cli('show_volume_qos')
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_get_qos_category, '
|
||||
'show_volume_qos failed. '
|
||||
'Return code: %(rc)lu, '
|
||||
@ -3427,7 +3395,7 @@ class FJDXCommon(object):
|
||||
rc, emsg, clidata = self._exec_eternus_cli(
|
||||
'set_qos_bandwidth_limit', **param_dict)
|
||||
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_set_limit, '
|
||||
'set_qos_bandwidth_limit failed. '
|
||||
'Return code: %(rc)lu, '
|
||||
@ -3506,7 +3474,7 @@ class FJDXCommon(object):
|
||||
WaitForCopyState=self._pywbem_uint(8, '16'),
|
||||
Synchronization=sdvsession)
|
||||
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('revert_to_snapshot, '
|
||||
'_exec_eternus_service error, '
|
||||
'volume: %(volume)s, '
|
||||
@ -3541,7 +3509,7 @@ class FJDXCommon(object):
|
||||
'start_copy_opc',
|
||||
**param_dict)
|
||||
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('revert_to_snapshot, '
|
||||
'start_copy_opc failed. '
|
||||
'Return code: %(rc)lu, '
|
||||
@ -3571,7 +3539,7 @@ class FJDXCommon(object):
|
||||
**param
|
||||
)
|
||||
|
||||
if rc != 0:
|
||||
if rc != CONSTANTS.RC_OK:
|
||||
msg = (_('_get_copy_sessions_list, '
|
||||
'get copy sessions failed. '
|
||||
'Return code: %(rc)lu, '
|
||||
|
@ -102,6 +102,25 @@ Perform the following steps using ETERNUS Web GUI or ETERNUS CLI.
|
||||
#. Ensure LAN connection between cinder controller and MNT port of ETERNUS DX
|
||||
and SAN connection between Compute nodes and CA ports of ETERNUS DX.
|
||||
|
||||
#. (Optional) If you want to use a public key to SSH to the ETERNUS DX storage,
|
||||
generate the SSH key, and upload the ``eternus.ietf`` file to the ETERNUS
|
||||
storage.
|
||||
|
||||
For information about how to set the public key, refer to the ETERNUS Web
|
||||
GUI manuals.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ssh-keygen -t rsa -N "" -f ./eternus -m PEM
|
||||
$ ssh-keygen -e -f ./eternus.pub > ./eternus.ietf
|
||||
|
||||
If the public key(eternus.ietf) that was created is deleted by mistake, use
|
||||
the following command to recreate the key.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ssh-keygen -e -f /root/.ssh/eternus.pub > ./eternus.ietf
|
||||
|
||||
Configuration
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
@ -219,11 +238,14 @@ Configuration example
|
||||
volume_driver = cinder.volume.drivers.fujitsu.eternus_dx.eternus_dx_fc.FJDXFCDriver
|
||||
cinder_eternus_config_file = /etc/cinder/fc.xml
|
||||
volume_backend_name = FC
|
||||
fujitsu_passwordless = False
|
||||
|
||||
[DXISCSI]
|
||||
volume_driver = cinder.volume.drivers.fujitsu.eternus_dx.eternus_dx_iscsi.FJDXISCSIDriver
|
||||
cinder_eternus_config_file = /etc/cinder/iscsi.xml
|
||||
volume_backend_name = ISCSI
|
||||
fujitsu_passwordless = True
|
||||
fujitsu_private_key_path = /etc/cinder/eternus
|
||||
|
||||
#. Create the driver configuration files ``fc.xml`` and ``iscsi.xml``.
|
||||
|
||||
|
@ -0,0 +1,40 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Fujitsu Eternus DX driver: Added support SSH key.
|
||||
|
||||
Added the method for connecting to Eternus Storage using SSH key.
|
||||
The connection method can be selected by setting the value of parameter
|
||||
``fujitsu_passwordless``, which has a default value of ``True``.
|
||||
|
||||
* When ``fujitsu_passwordless`` is set to ``True``, SSH key is used for
|
||||
connecting to the storage. Additionally, ``fujitsu_private_key_path``
|
||||
needs to be set to the path of the SSH private key.
|
||||
|
||||
* When ``fujitsu_passwordless`` is set to ``False``, password is used for
|
||||
SSH connection to the storage.
|
||||
|
||||
See the `Fujitsu ETERNUS DX driver documentation
|
||||
<https://docs.openstack.org/cinder/latest/configuration/block-storage/drivers/fujitsu-eternus-dx-driver.html>`_
|
||||
for details.
|
||||
upgrade:
|
||||
- |
|
||||
Fujitsu Eternus DX driver: Added SSH key and password connection switching
|
||||
|
||||
Added the method for connecting to Eternus Storage using SSH key.
|
||||
The connection method can be selected by setting the value of parameter
|
||||
``fujitsu_passwordless``, which has a default value of ``True``.
|
||||
|
||||
For upgrading from previous versions that relied on password authentication,
|
||||
you must explicitly set ``fujitsu_passwordless = False`` in the
|
||||
configuration. This ensures backward compatibility with the legacy
|
||||
password-based workflow.
|
||||
|
||||
The default True value enforces key-based auth for new deployments, aligning
|
||||
with security best practices at the cost of a minor configuration adjustment
|
||||
for existing users.
|
||||
fixes:
|
||||
- |
|
||||
Fujitsu Eternus DX driver `bug #2048850
|
||||
<https://bugs.launchpad.net/cinder/+bug/2048850>`_:
|
||||
Added parsing of error messages when CLI execution fails.
|
Loading…
x
Reference in New Issue
Block a user