Fujitsu Driver: Update extend volume functionality

Revised the 'Extend Volume' process on the RaidGroup to improve
processing speed as follows:

* When extending a volume created on ThinProvisionPool, the process
  will still use SMI-S for volume extension.

* When extending a volume created on RAID group, the process has been
  updated to use CLI for volume extension.

Change-Id: Ifcd93b9c9d94b214e890c57afd588b43b568478a
This commit is contained in:
inori 2023-09-27 06:11:30 -04:00
parent 8a9f9a7c36
commit 1491eecfd3
7 changed files with 181 additions and 87 deletions

View File

@ -176,7 +176,7 @@ FAKE_POOLS = [{
}] }]
FAKE_STATS = { FAKE_STATS = {
'driver_version': '1.4.0', 'driver_version': '1.4.1',
'storage_protocol': 'iSCSI', 'storage_protocol': 'iSCSI',
'vendor_name': 'FUJITSU', 'vendor_name': 'FUJITSU',
'QoS_support': True, 'QoS_support': True,
@ -186,7 +186,7 @@ FAKE_STATS = {
'pools': FAKE_POOLS, 'pools': FAKE_POOLS,
} }
FAKE_STATS2 = { FAKE_STATS2 = {
'driver_version': '1.4.0', 'driver_version': '1.4.1',
'storage_protocol': 'FC', 'storage_protocol': 'FC',
'vendor_name': 'FUJITSU', 'vendor_name': 'FUJITSU',
'QoS_support': True, 'QoS_support': True,
@ -1008,6 +1008,8 @@ class FJFCDriverTestCase(test.TestCase):
'3B\r\nf.ce\tMaintainer\t01\t00' '3B\r\nf.ce\tMaintainer\t01\t00'
'\t00\t00\r\ntestuser\tSoftware' '\t00\t00\r\ntestuser\tSoftware'
'\t01\t01\t00\t00\r\nCLI> ' % exec_cmdline) '\t01\t01\t00\t00\r\nCLI> ' % exec_cmdline)
elif exec_cmdline.startswith('expand volume'):
ret = '%s\r\n00\r\nCLI> ' % exec_cmdline
elif exec_cmdline.startswith('set volume-qos'): elif exec_cmdline.startswith('set volume-qos'):
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
elif exec_cmdline.startswith('show volumes'): elif exec_cmdline.startswith('show volumes'):
@ -1176,19 +1178,27 @@ class FJFCDriverTestCase(test.TestCase):
self.driver.delete_volume(TEST_VOLUME) self.driver.delete_volume(TEST_VOLUME)
def test_extend_volume(self): def test_extend_volume(self):
model_info = self.driver.create_volume(TEST_VOLUME) # Test the extension of volume created on RaidGroup and
self.assertEqual(FAKE_MODEL_INFO1, model_info) # ThinProvisioningPool separately.
TEST_VOLUME_LIST = [TEST_VOLUME, TEST_VOLUME2]
FAKE_MODEL_INFO_LIST = [FAKE_MODEL_INFO1, FAKE_MODEL_INFO3]
model_info_list = []
for i in range(len(TEST_VOLUME_LIST)):
model_info = self.driver.create_volume(TEST_VOLUME_LIST[i])
self.assertEqual(FAKE_MODEL_INFO_LIST[i], model_info)
model_info_list.append(model_info)
volume_info = {} for i in range(len(TEST_VOLUME_LIST)):
for key in TEST_VOLUME: volume_info = {}
if key == 'provider_location': for key in TEST_VOLUME_LIST[i]:
volume_info[key] = model_info[key] if key == 'provider_location':
elif key == 'metadata': volume_info[key] = model_info_list[i][key]
volume_info[key] = model_info[key] elif key == 'metadata':
else: volume_info[key] = model_info_list[i][key]
volume_info[key] = TEST_VOLUME[key] else:
volume_info[key] = TEST_VOLUME_LIST[i][key]
self.driver.extend_volume(volume_info, 10) self.driver.extend_volume(volume_info, 10)
def test_create_volume_with_qos(self): def test_create_volume_with_qos(self):
self.driver.common._get_qos_specs = mock.Mock() self.driver.common._get_qos_specs = mock.Mock()
@ -1245,6 +1255,8 @@ class FJISCSIDriverTestCase(test.TestCase):
'3B\r\nf.ce\tMaintainer\t01\t00' '3B\r\nf.ce\tMaintainer\t01\t00'
'\t00\t00\r\ntestuser\tSoftware' '\t00\t00\r\ntestuser\tSoftware'
'\t01\t01\t00\t00\r\nCLI> ' % exec_cmdline) '\t01\t01\t00\t00\r\nCLI> ' % exec_cmdline)
elif exec_cmdline.startswith('expand volume'):
ret = '%s\r\n00\r\nCLI> ' % exec_cmdline
elif exec_cmdline.startswith('set volume-qos'): elif exec_cmdline.startswith('set volume-qos'):
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
elif exec_cmdline.startswith('show volumes'): elif exec_cmdline.startswith('show volumes'):
@ -1414,19 +1426,27 @@ class FJISCSIDriverTestCase(test.TestCase):
self.driver.delete_volume(TEST_VOLUME) self.driver.delete_volume(TEST_VOLUME)
def test_extend_volume(self): def test_extend_volume(self):
model_info = self.driver.create_volume(TEST_VOLUME) # Test the extension of volume created on RaidGroup and
self.assertEqual(FAKE_MODEL_INFO1, model_info) # ThinProvisioningPool separately.
TEST_VOLUME_LIST = [TEST_VOLUME, TEST_VOLUME2]
FAKE_MODEL_INFO_LIST = [FAKE_MODEL_INFO1, FAKE_MODEL_INFO3]
model_info_list = []
for i in range(len(TEST_VOLUME_LIST)):
model_info = self.driver.create_volume(TEST_VOLUME_LIST[i])
self.assertEqual(FAKE_MODEL_INFO_LIST[i], model_info)
model_info_list.append(model_info)
volume_info = {} for i in range(len(TEST_VOLUME_LIST)):
for key in TEST_VOLUME: volume_info = {}
if key == 'provider_location': for key in TEST_VOLUME_LIST[i]:
volume_info[key] = model_info[key] if key == 'provider_location':
elif key == 'metadata': volume_info[key] = model_info_list[i][key]
volume_info[key] = model_info[key] elif key == 'metadata':
else: volume_info[key] = model_info_list[i][key]
volume_info[key] = TEST_VOLUME[key] else:
volume_info[key] = TEST_VOLUME_LIST[i][key]
self.driver.extend_volume(volume_info, 10) self.driver.extend_volume(volume_info, 10)
def test_create_volume_with_qos(self): def test_create_volume_with_qos(self):
self.driver.common._get_qos_specs = mock.Mock() self.driver.common._get_qos_specs = mock.Mock()
@ -1467,6 +1487,8 @@ class FJCLITestCase(test.TestCase):
'3B\r\nf.ce\tMaintainer\t01\t00' '3B\r\nf.ce\tMaintainer\t01\t00'
'\t00\t00\r\ntestuser\tSoftware' '\t00\t00\r\ntestuser\tSoftware'
'\t01\t01\t00\t00\r\nCLI> ' % exec_cmdline) '\t01\t01\t00\t00\r\nCLI> ' % exec_cmdline)
elif exec_cmdline.startswith('expand volume'):
ret = '%s\r\n00\r\nCLI> ' % exec_cmdline
elif exec_cmdline.startswith('set volume-qos'): elif exec_cmdline.startswith('set volume-qos'):
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
elif exec_cmdline.startswith('show volumes'): elif exec_cmdline.startswith('show volumes'):
@ -1561,6 +1583,19 @@ class FJCLITestCase(test.TestCase):
role = self.cli._check_user_role() role = self.cli._check_user_role()
self.assertEqual(FAKE_ROLE, role) self.assertEqual(FAKE_ROLE, role)
def test_expand_volume(self):
FAKE_VOLME_NAME = 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
FAKE_RG_NAME = 'abcd1234_RG'
FAKE_SIZE = '10gb'
FAKE_EXPAND_OPTION = self.create_fake_options(
volume_name=FAKE_VOLME_NAME,
rg_name=FAKE_RG_NAME,
size=FAKE_SIZE)
EXPAND_OUTPUT = self.cli._expand_volume(**FAKE_EXPAND_OPTION)
FAKE_EXPAND_OUTPUT = {**FAKE_CLI_OUTPUT, 'message': []}
self.assertEqual(FAKE_EXPAND_OUTPUT, EXPAND_OUTPUT)
def test_set_volume_qos(self): def test_set_volume_qos(self):
FAKE_VOLUME_NAME = 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg==' FAKE_VOLUME_NAME = 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
FAKE_BANDWIDTH_LIMIT = 2 FAKE_BANDWIDTH_LIMIT = 2

View File

@ -47,6 +47,7 @@ class FJDXCLI(object):
self.ce_support = False self.ce_support = False
self.CMD_dic = { self.CMD_dic = {
'check_user_role': self._check_user_role, 'check_user_role': self._check_user_role,
'expand_volume': self._expand_volume,
'show_pool_provision': self._show_pool_provision, 'show_pool_provision': self._show_pool_provision,
'show_qos_bandwidth_limit': self._show_qos_bandwidth_limit, 'show_qos_bandwidth_limit': self._show_qos_bandwidth_limit,
'set_qos_bandwidth_limit': self._set_qos_bandwidth_limit, 'set_qos_bandwidth_limit': self._set_qos_bandwidth_limit,
@ -239,6 +240,10 @@ class FJDXCLI(object):
} }
return output return output
def _expand_volume(self, **option):
"""Exec expand volume."""
return self._exec_cli("expand volume", **option)
def _set_volume_qos(self, **option): def _set_volume_qos(self, **option):
"""Exec set volume-qos.""" """Exec set volume-qos."""
return self._exec_cli("set volume-qos", **option) return self._exec_cli("set volume-qos", **option)

View File

@ -67,10 +67,11 @@ class FJDXCommon(object):
1.0 - Initial driver 1.0 - Initial driver
1.3.0 - Community base version 1.3.0 - Community base version
1.4.0 - Add support for QoS. 1.4.0 - Add support for QoS.
1.4.1 - Add the method for expanding RAID volumes by CLI.
""" """
VERSION = "1.4.0" VERSION = "1.4.1"
stats = { stats = {
'driver_version': VERSION, 'driver_version': VERSION,
'storage_protocol': None, 'storage_protocol': None,
@ -821,32 +822,33 @@ class FJDXCommon(object):
'size': volume['size'], 'nsize': new_size}) 'size': volume['size'], 'nsize': new_size})
self.conn = self._get_eternus_connection() self.conn = self._get_eternus_connection()
volumesize = new_size * units.Gi
volumename = self._get_volume_name(volume) volumename = self._get_volume_name(volume)
# Get source volume instance. # Get volume instance.
vol_instance = self._find_lun(volume) volume_instance = self._find_lun(volume)
if vol_instance is None: if not volume_instance:
msg = (_('extend_volume, ' msg = (_('extend_volume, '
'volumename: %(volumename)s, ' 'volumename: %(volumename)s, '
'volume not found.') 'not found.')
% {'volumename': volumename}) % {'volumename': volumename})
LOG.error(msg) LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg) raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('extend_volume, volumename: %(volumename)s, ' LOG.debug('extend_volume, volumename: %(volumename)s, '
'volumesize: %(volumesize)u, ' 'volumesize: %(volumesize)u, '
'volume instance: %(vol_instance)s.', 'volume instance: %(volume_instance)s.',
{'volumename': volumename, {'volumename': volumename,
'volumesize': volumesize, 'volumesize': new_size,
'vol_instance': vol_instance.path}) 'volume_instance': volume_instance.path})
# Get poolname from driver configuration file. # Get poolname from driver configuration file.
pool_name, pool = self._find_pool_from_volume(vol_instance) pool_name, pool = self._find_pool_from_volume(volume_instance)
if pool is None:
# Check the existence of pool.
if not pool:
msg = (_('extend_volume, ' msg = (_('extend_volume, '
'eternus_pool: %(eternus_pool)s, ' 'eternus_pool: %(eternus_pool)s, '
'pool not found.') 'not found.')
% {'eternus_pool': pool_name}) % {'eternus_pool': pool_name})
LOG.error(msg) LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg) raise exception.VolumeBackendAPIException(data=msg)
@ -857,56 +859,84 @@ class FJDXCommon(object):
else: else:
pooltype = CONSTANTS.TPPOOL pooltype = CONSTANTS.TPPOOL
configservice = self._find_eternus_service(CONSTANTS.STOR_CONF) if pooltype == CONSTANTS.RAIDGROUP:
if not configservice: extend_size = str(new_size - volume['size']) + 'gb'
msg = (_('extend_volume, volume: %(volume)s, ' param_dict = {
'volumename: %(volumename)s, ' 'volume-name': volumename,
'eternus_pool: %(eternus_pool)s, ' 'rg-name': pool_name,
'Storage Configuration Service not found.') 'size': extend_size
% {'volume': volume, }
'volumename': volumename, rc, errordesc, data = self._exec_eternus_cli(
'eternus_pool': pool_name}) 'expand_volume',
LOG.error(msg) **param_dict)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('extend_volume, ' if rc != 0:
'CreateOrModifyElementFromStoragePool, ' msg = (_('extend_volume, '
'ConfigService: %(service)s, ' 'volumename: %(volumename)s, '
'ElementName: %(volumename)s, ' 'Return code: %(rc)lu, '
'InPool: %(eternus_pool)s, ' 'Error: %(errordesc)s, '
'ElementType: %(pooltype)u, ' 'Message: %(job)s, '
'Size: %(volumesize)u, ' 'PoolType: %(pooltype)s.')
'TheElement: %(vol_instance)s.', % {'volumename': volumename,
{'service': configservice, 'rc': rc,
'volumename': volumename, 'errordesc': errordesc,
'eternus_pool': pool_name, 'pooltype': CONSTANTS.POOL_TYPE_dic[pooltype],
'pooltype': pooltype, 'job': data})
'volumesize': volumesize, LOG.error(msg)
'vol_instance': vol_instance.path}) raise exception.VolumeBackendAPIException(data=msg)
# Invoke method for extend volume else: # Pooltype is TPPOOL.
rc, errordesc, job = self._exec_eternus_service( volumesize = new_size * units.Gi
'CreateOrModifyElementFromStoragePool', configservice = self._find_eternus_service(CONSTANTS.STOR_CONF)
configservice, if not configservice:
ElementName=volumename, msg = (_('extend_volume, volume: %(volume)s, '
InPool=pool, 'volumename: %(volumename)s, '
ElementType=self._pywbem_uint(pooltype, '16'), 'eternus_pool: %(eternus_pool)s, '
Size=self._pywbem_uint(volumesize, '64'), 'Storage Configuration Service not found.')
TheElement=vol_instance.path) % {'volume': volume,
'volumename': volumename,
'eternus_pool': pool_name})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if rc != 0: LOG.debug('extend_volume, '
msg = (_('extend_volume, ' 'CreateOrModifyElementFromStoragePool, '
'volumename: %(volumename)s, ' 'ConfigService: %(service)s, '
'Return code: %(rc)lu, ' 'ElementName: %(volumename)s, '
'Error: %(errordesc)s, ' 'InPool: %(eternus_pool)s, '
'PoolType: %(pooltype)s.') 'ElementType: %(pooltype)u, '
% {'volumename': volumename, 'Size: %(volumesize)u, '
'rc': rc, 'TheElement: %(vol_instance)s.',
'errordesc': errordesc, {'service': configservice,
'pooltype': CONSTANTS.POOL_TYPE_dic[pooltype]}) 'volumename': volumename,
'eternus_pool': pool_name,
'pooltype': pooltype,
'volumesize': volumesize,
'vol_instance': volume_instance.path})
LOG.error(msg) # Invoke method for extend volume.
raise exception.VolumeBackendAPIException(data=msg) rc, errordesc, _x = self._exec_eternus_service(
'CreateOrModifyElementFromStoragePool',
configservice,
ElementName=volumename,
InPool=pool,
ElementType=self._pywbem_uint(pooltype, '16'),
Size=self._pywbem_uint(volumesize, '64'),
TheElement=volume_instance.path)
if rc != 0:
msg = (_('extend_volume, '
'volumename: %(volumename)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s, '
'PoolType: %(pooltype)s.')
% {'volumename': volumename,
'rc': rc,
'errordesc': errordesc,
'pooltype': CONSTANTS.POOL_TYPE_dic[pooltype]})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('extend_volume, ' LOG.debug('extend_volume, '
'volumename: %(volumename)s, ' 'volumename: %(volumename)s, '

View File

@ -157,7 +157,13 @@ class FJDXFCDriver(driver.FibreChannelDriver):
def extend_volume(self, volume, new_size): def extend_volume(self, volume, new_size):
"""Extend volume.""" """Extend volume."""
self.common.extend_volume(volume, new_size) LOG.debug('extend_volume, '
'volume id: %s, Enter method.', volume['id'])
used_pool_name = self.common.extend_volume(volume, new_size)
LOG.debug('extend_volume, '
'used pool name: %s, Exit method.', used_pool_name)
def _get_metadata(self, volume): def _get_metadata(self, volume):
v_metadata = volume.get('volume_metadata') v_metadata = volume.get('volume_metadata')

View File

@ -144,4 +144,10 @@ class FJDXISCSIDriver(driver.ISCSIDriver):
def extend_volume(self, volume, new_size): def extend_volume(self, volume, new_size):
"""Extend volume.""" """Extend volume."""
self.common.extend_volume(volume, new_size) LOG.debug('extend_volume, '
'volume id: %s, Enter method.', volume['id'])
used_pool_name = self.common.extend_volume(volume, new_size)
LOG.debug('extend_volume, '
'used pool name: %s, Exit method.', used_pool_name)

View File

@ -44,11 +44,9 @@ Supported operations
* Copy an image to a volume. * Copy an image to a volume.
* Copy a volume to an image. * Copy a volume to an image.
* Clone a volume. * Clone a volume.
* Extend a volume. (\*1) * Extend a volume.
* Get volume statistics. * Get volume statistics.
(\*1): It is executable only when you use TPP as a storage pool.
Preparation Preparation
~~~~~~~~~~~ ~~~~~~~~~~~

View File

@ -0,0 +1,14 @@
---
features:
- |
Fujitsu ETERNUS DX driver: Added support to extend a volume on RAID Group
using CLI.
Revised the 'Extend Volume' process on the RAID Group to improve processing
speed as follows:
* When extending a volume created on ThinProvisionPool, the process will
still use SMI-S for volume extension.
* When extending a volume created on RaidGroup, the process has been
updated to use CLI for volume extension.