Merge "Retype support for CloudByte iSCSI cinder driver"
This commit is contained in:
commit
940b6883e2
cinder
@ -25,9 +25,12 @@ import mock
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.drivers.cloudbyte import cloudbyte
|
||||
from cinder.volume import qos_specs
|
||||
from cinder.volume import volume_types
|
||||
|
||||
# A fake list account response of cloudbyte's elasticenter
|
||||
FAKE_LIST_ACCOUNT_RESPONSE = """{ "listAccountResponse" : {
|
||||
@ -650,6 +653,7 @@ class CloudByteISCSIDriverTestCase(testtools.TestCase):
|
||||
def setUp(self):
|
||||
super(CloudByteISCSIDriverTestCase, self).setUp()
|
||||
self._configure_driver()
|
||||
self.ctxt = context.get_admin_context()
|
||||
|
||||
def _configure_driver(self):
|
||||
|
||||
@ -753,6 +757,14 @@ class CloudByteISCSIDriverTestCase(testtools.TestCase):
|
||||
|
||||
return MAP_COMMAND_TO_FAKE_RESPONSE[cmd]
|
||||
|
||||
def _fake_api_req_to_list_filesystem(
|
||||
self, cmd, params, version='1.0'):
|
||||
"""This is a side effect function."""
|
||||
if cmd == 'listFileSystem':
|
||||
return {"listFilesystemResponse": {"filesystem": [{}]}}
|
||||
|
||||
return MAP_COMMAND_TO_FAKE_RESPONSE[cmd]
|
||||
|
||||
def _side_effect_api_req_to_list_vol_iscsi_service(
|
||||
self, cmd, params, version='1.0'):
|
||||
"""This is a side effect function."""
|
||||
@ -824,6 +836,20 @@ class CloudByteISCSIDriverTestCase(testtools.TestCase):
|
||||
|
||||
return volume_id
|
||||
|
||||
def _fake_get_volume_type(self, ctxt, type_id):
|
||||
fake_type = {'qos_specs_id': 'fake-id',
|
||||
'extra_specs': {'qos:iops': '100000'},
|
||||
'id': 'fake-volume-type-id'}
|
||||
|
||||
return fake_type
|
||||
|
||||
def _fake_get_qos_spec(self, ctxt, spec_id):
|
||||
fake_qos_spec = {'id': 'fake-qos-spec-id',
|
||||
'specs': {'iops': '1000',
|
||||
'graceallowed': 'true',
|
||||
'readonly': 'true'}}
|
||||
return fake_qos_spec
|
||||
|
||||
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
|
||||
'_execute_and_get_response_details')
|
||||
def test_api_request_for_cloudbyte(self, mock_conn):
|
||||
@ -1030,7 +1056,8 @@ class CloudByteISCSIDriverTestCase(testtools.TestCase):
|
||||
|
||||
volume = {
|
||||
'id': fake_volume_id,
|
||||
'size': 22
|
||||
'size': 22,
|
||||
'volume_type_id': None
|
||||
}
|
||||
|
||||
# Test - I
|
||||
@ -1469,3 +1496,84 @@ class CloudByteISCSIDriverTestCase(testtools.TestCase):
|
||||
"backend API: No response was received from CloudByte "
|
||||
"storage list tsm API call."):
|
||||
self.driver.get_volume_stats(refresh=True)
|
||||
|
||||
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
|
||||
'_api_request_for_cloudbyte')
|
||||
@mock.patch.object(volume_types,
|
||||
'get_volume_type')
|
||||
@mock.patch.object(qos_specs,
|
||||
'get_qos_specs')
|
||||
def test_retype(self, get_qos_spec, get_volume_type, mock_api_req):
|
||||
|
||||
# prepare the input test data
|
||||
fake_new_type = {'id': 'fake-new-type-id'}
|
||||
fake_volume_id = self._get_fake_volume_id()
|
||||
|
||||
volume = {
|
||||
'id': 'SomeID',
|
||||
'provider_id': fake_volume_id
|
||||
}
|
||||
|
||||
# configure the mocks with respective side-effects
|
||||
mock_api_req.side_effect = self._side_effect_api_req
|
||||
get_qos_spec.side_effect = self._fake_get_qos_spec
|
||||
get_volume_type.side_effect = self._fake_get_volume_type
|
||||
|
||||
self.assertTrue(self.driver.retype(self.ctxt,
|
||||
volume,
|
||||
fake_new_type, None, None))
|
||||
|
||||
# assert the invoked api calls
|
||||
self.assertEqual(3, mock_api_req.call_count)
|
||||
|
||||
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
|
||||
'_api_request_for_cloudbyte')
|
||||
@mock.patch.object(volume_types,
|
||||
'get_volume_type')
|
||||
@mock.patch.object(qos_specs,
|
||||
'get_qos_specs')
|
||||
def test_retype_without_provider_id(self, get_qos_spec, get_volume_type,
|
||||
mock_api_req):
|
||||
|
||||
# prepare the input test data
|
||||
fake_new_type = {'id': 'fake-new-type-id'}
|
||||
volume = {'id': 'SomeID'}
|
||||
|
||||
# configure the mocks with respective side-effects
|
||||
mock_api_req.side_effect = self._side_effect_api_req
|
||||
get_qos_spec.side_effect = self._fake_get_qos_spec
|
||||
get_volume_type.side_effect = self._fake_get_volume_type
|
||||
|
||||
# Now run the test & assert the exception
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.retype,
|
||||
self.ctxt, volume, fake_new_type, None, None)
|
||||
|
||||
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
|
||||
'_api_request_for_cloudbyte')
|
||||
@mock.patch.object(volume_types,
|
||||
'get_volume_type')
|
||||
@mock.patch.object(qos_specs,
|
||||
'get_qos_specs')
|
||||
def test_retype_without_filesystem(self, get_qos_spec, get_volume_type,
|
||||
mock_api_req):
|
||||
|
||||
# prepare the input test data
|
||||
fake_new_type = {'id': 'fake-new-type-id'}
|
||||
fake_volume_id = self._get_fake_volume_id()
|
||||
|
||||
volume = {
|
||||
'id': 'SomeID',
|
||||
'provider_id': fake_volume_id
|
||||
}
|
||||
|
||||
# configure the mocks with respective side-effects
|
||||
mock_api_req.side_effect = self._side_effect_api_req
|
||||
get_qos_spec.side_effect = self._fake_get_qos_spec
|
||||
get_volume_type.side_effect = self._fake_get_volume_type
|
||||
mock_api_req.side_effect = self._fake_api_req_to_list_filesystem
|
||||
|
||||
# Now run the test & assert the exception
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.retype,
|
||||
self.ctxt, volume, fake_new_type, None, None)
|
||||
|
@ -23,10 +23,13 @@ import six
|
||||
from six.moves import http_client
|
||||
from six.moves import urllib
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LE, _LI
|
||||
from cinder.volume.drivers.cloudbyte import options
|
||||
from cinder.volume.drivers.san import san
|
||||
from cinder.volume import qos_specs
|
||||
from cinder.volume import volume_types
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -39,9 +42,10 @@ class CloudByteISCSIDriver(san.SanISCSIDriver):
|
||||
1.1.0 - Add chap support and minor bug fixes
|
||||
1.1.1 - Add wait logic for delete volumes
|
||||
1.1.2 - Update ig to None before delete volume
|
||||
1.2.0 - Add retype support
|
||||
"""
|
||||
|
||||
VERSION = '1.1.2'
|
||||
VERSION = '1.2.0'
|
||||
volume_stats = {}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -50,6 +54,8 @@ class CloudByteISCSIDriver(san.SanISCSIDriver):
|
||||
options.cloudbyte_add_qosgroup_opts)
|
||||
self.configuration.append_config_values(
|
||||
options.cloudbyte_create_volume_opts)
|
||||
self.configuration.append_config_values(
|
||||
options.cloudbyte_update_volume_opts)
|
||||
self.configuration.append_config_values(
|
||||
options.cloudbyte_connection_opts)
|
||||
self.cb_use_chap = self.configuration.use_chap_auth
|
||||
@ -182,35 +188,25 @@ class CloudByteISCSIDriver(san.SanISCSIDriver):
|
||||
data = self._api_request_for_cloudbyte("listTsm", params)
|
||||
return data
|
||||
|
||||
def _override_params(self, default_dict, filtered_user_dict):
|
||||
"""Override the default config values with user provided values."""
|
||||
|
||||
if filtered_user_dict is None:
|
||||
# Nothing to override
|
||||
return default_dict
|
||||
|
||||
for key, value in default_dict.items():
|
||||
# Fill the user dict with default options based on condition
|
||||
if filtered_user_dict.get(key) is None and value is not None:
|
||||
filtered_user_dict[key] = value
|
||||
|
||||
return filtered_user_dict
|
||||
|
||||
def _add_qos_group_request(self, volume, tsmid, volume_name):
|
||||
def _add_qos_group_request(self, volume, tsmid, volume_name,
|
||||
qos_group_params):
|
||||
# Prepare the user input params
|
||||
params = {
|
||||
"name": "QoS_" + volume_name,
|
||||
"tsmid": tsmid
|
||||
}
|
||||
# Get qos related params from configuration
|
||||
params = self.configuration.cb_add_qosgroup
|
||||
params.update(self.configuration.cb_add_qosgroup)
|
||||
|
||||
if params is None:
|
||||
params = {}
|
||||
|
||||
params['name'] = "QoS_" + volume_name
|
||||
params['tsmid'] = tsmid
|
||||
# Override the default configuration by qos specs
|
||||
if qos_group_params:
|
||||
params.update(qos_group_params)
|
||||
|
||||
data = self._api_request_for_cloudbyte("addQosGroup", params)
|
||||
return data
|
||||
|
||||
def _create_volume_request(self, volume, datasetid, qosgroupid,
|
||||
tsmid, volume_name):
|
||||
tsmid, volume_name, file_system_params):
|
||||
|
||||
size = volume.get('size')
|
||||
quotasize = six.text_type(size) + "G"
|
||||
@ -225,8 +221,11 @@ class CloudByteISCSIDriver(san.SanISCSIDriver):
|
||||
}
|
||||
|
||||
# Get the additional params from configuration
|
||||
params = self._override_params(self.configuration.cb_create_volume,
|
||||
params)
|
||||
params.update(self.configuration.cb_create_volume)
|
||||
|
||||
# Override the default configuration by qos specs
|
||||
if file_system_params:
|
||||
params.update(file_system_params)
|
||||
|
||||
data = self._api_request_for_cloudbyte("createVolume", params)
|
||||
return data
|
||||
@ -756,8 +755,40 @@ class CloudByteISCSIDriver(san.SanISCSIDriver):
|
||||
{'vol': volume_id,
|
||||
'ig': ig_name})
|
||||
|
||||
def _get_qos_by_volume_type(self, ctxt, type_id):
|
||||
"""Get the properties which can be QoS or file system related."""
|
||||
|
||||
update_qos_group_params = {}
|
||||
update_file_system_params = {}
|
||||
|
||||
volume_type = volume_types.get_volume_type(ctxt, type_id)
|
||||
qos_specs_id = volume_type.get('qos_specs_id')
|
||||
extra_specs = volume_type.get('extra_specs')
|
||||
|
||||
if qos_specs_id is not None:
|
||||
specs = qos_specs.get_qos_specs(ctxt, qos_specs_id)['specs']
|
||||
|
||||
# Override extra specs with specs
|
||||
# Hence specs will prefer QoS than extra specs
|
||||
extra_specs.update(specs)
|
||||
|
||||
for key, value in extra_specs.items():
|
||||
if ':' in key:
|
||||
fields = key.split(':')
|
||||
key = fields[1]
|
||||
|
||||
if key in self.configuration.cb_update_qos_group:
|
||||
update_qos_group_params[key] = value
|
||||
|
||||
elif key in self.configuration.cb_update_file_system:
|
||||
update_file_system_params[key] = value
|
||||
|
||||
return update_qos_group_params, update_file_system_params
|
||||
|
||||
def create_volume(self, volume):
|
||||
|
||||
qos_group_params = {}
|
||||
file_system_params = {}
|
||||
tsm_name = self.configuration.cb_tsm_name
|
||||
account_name = self.configuration.cb_account_name
|
||||
|
||||
@ -767,6 +798,13 @@ class CloudByteISCSIDriver(san.SanISCSIDriver):
|
||||
# Set backend storage volume name using OpenStack volume id
|
||||
cb_volume_name = volume['id'].replace("-", "")
|
||||
|
||||
ctxt = context.get_admin_context()
|
||||
type_id = volume['volume_type_id']
|
||||
|
||||
if type_id is not None:
|
||||
qos_group_params, file_system_params = (
|
||||
self._get_qos_by_volume_type(ctxt, type_id))
|
||||
|
||||
LOG.debug("Will create a volume [%(cb_vol)s] in TSM [%(tsm)s] "
|
||||
"at CloudByte storage w.r.t "
|
||||
"OpenStack volume [%(stack_vol)s].",
|
||||
@ -781,7 +819,7 @@ class CloudByteISCSIDriver(san.SanISCSIDriver):
|
||||
LOG.debug("Creating qos group for CloudByte volume [%s].",
|
||||
cb_volume_name)
|
||||
qos_data = self._add_qos_group_request(
|
||||
volume, tsm_details.get('tsmid'), cb_volume_name)
|
||||
volume, tsm_details.get('tsmid'), cb_volume_name, qos_group_params)
|
||||
|
||||
# Extract the qos group id from response
|
||||
qosgroupid = qos_data['addqosgroupresponse']['qosgroup']['id']
|
||||
@ -792,7 +830,7 @@ class CloudByteISCSIDriver(san.SanISCSIDriver):
|
||||
# Send a create volume request to CloudByte API
|
||||
vol_data = self._create_volume_request(
|
||||
volume, tsm_details.get('datasetid'), qosgroupid,
|
||||
tsm_details.get('tsmid'), cb_volume_name)
|
||||
tsm_details.get('tsmid'), cb_volume_name, file_system_params)
|
||||
|
||||
# Since create volume is an async call;
|
||||
# need to confirm the creation before proceeding further
|
||||
@ -1134,3 +1172,51 @@ class CloudByteISCSIDriver(san.SanISCSIDriver):
|
||||
self.volume_stats = data
|
||||
|
||||
return self.volume_stats
|
||||
|
||||
def retype(self, ctxt, volume, new_type, diff, host):
|
||||
"""Retypes a volume, QoS and file system update is only done."""
|
||||
|
||||
cb_volume_id = volume.get('provider_id')
|
||||
|
||||
if cb_volume_id is None:
|
||||
message = _("Provider information w.r.t CloudByte storage "
|
||||
"was not found for OpenStack "
|
||||
"volume [%s].") % volume['id']
|
||||
|
||||
raise exception.VolumeBackendAPIException(message)
|
||||
|
||||
update_qos_group_params, update_file_system_params = (
|
||||
self._get_qos_by_volume_type(ctxt, new_type['id']))
|
||||
|
||||
if update_qos_group_params:
|
||||
list_file_sys_params = {'id': cb_volume_id}
|
||||
response = self._api_request_for_cloudbyte(
|
||||
'listFileSystem', list_file_sys_params)
|
||||
|
||||
response = response['listFilesystemResponse']
|
||||
cb_volume_list = response['filesystem']
|
||||
cb_volume = cb_volume_list[0]
|
||||
|
||||
if not cb_volume:
|
||||
msg = (_("Volume [%(cb_vol)s] was not found at "
|
||||
"CloudByte storage corresponding to OpenStack "
|
||||
"volume [%(ops_vol)s].") %
|
||||
{'cb_vol': cb_volume_id, 'ops_vol': volume['id']})
|
||||
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
update_qos_group_params['id'] = cb_volume.get('groupid')
|
||||
|
||||
self._api_request_for_cloudbyte(
|
||||
'updateQosGroup', update_qos_group_params)
|
||||
|
||||
if update_file_system_params:
|
||||
update_file_system_params['id'] = cb_volume_id
|
||||
self._api_request_for_cloudbyte(
|
||||
'updateFileSystem', update_file_system_params)
|
||||
|
||||
LOG.info(_LI("Successfully updated CloudByte volume [%(cb_vol)s] "
|
||||
"corresponding to OpenStack volume [%(ops_vol)s]."),
|
||||
{'cb_vol': cb_volume_id, 'ops_vol': volume['id']})
|
||||
|
||||
return True
|
||||
|
@ -85,7 +85,18 @@ cloudbyte_create_volume_opts = [
|
||||
help="These values will be used for CloudByte storage's "
|
||||
"createVolume API call."), ]
|
||||
|
||||
cloudbyte_update_volume_opts = [
|
||||
cfg.ListOpt('cb_update_qos_group',
|
||||
default=["iops", "latency", "graceallowed"],
|
||||
help="These values will be used for CloudByte storage's "
|
||||
"updateQosGroup API call."),
|
||||
cfg.ListOpt('cb_update_file_system',
|
||||
default=["compression", "sync", "noofcopies", "readonly"],
|
||||
help="These values will be used for CloudByte storage's "
|
||||
"updateFileSystem API call."), ]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(cloudbyte_add_qosgroup_opts)
|
||||
CONF.register_opts(cloudbyte_create_volume_opts)
|
||||
CONF.register_opts(cloudbyte_connection_opts)
|
||||
CONF.register_opts(cloudbyte_update_volume_opts)
|
||||
|
Loading…
x
Reference in New Issue
Block a user