Raise correct exception when validate_connector failed

Cinder volume manager uses validate_connector() method to verify if required
information is in connector when handling initialize_connection() request.
validate_connector() is actually a pure input validation method, basically
checking if 'initiator' or 'wwpns' is in connector if storage protocol is
iSCSI or FC.  However, when required information is missing, currently drivers
raises either VolumeBackendAPIException or VolumeDriverException, which would
then bubble up to API and then to user (Nova) as InternalServerError.

This change adds a new exception - InvalidConnectorException, that drivers
should raise when connector is found not valid.  With that, Cinder API would
raise BadRequest instead to user, suggesting things are missing in request.

Change-Id: I4f04f5d0c558404836a2bd270f7f22f3f2d4f314
Closes-bug: #1409580
This commit is contained in:
Zhiteng Huang
2015-01-12 13:27:15 +08:00
parent 5f17a953ea
commit c7d97c4ad7
14 changed files with 51 additions and 39 deletions

View File

@@ -195,6 +195,9 @@ class VolumeActionsController(wsgi.Controller):
info = self.volume_api.initialize_connection(context, info = self.volume_api.initialize_connection(context,
volume, volume,
connector) connector)
except exception.InvalidInput as err:
raise webob.exc.HTTPBadRequest(
explanation=err)
except exception.VolumeBackendAPIException as error: except exception.VolumeBackendAPIException as error:
msg = _("Unable to fetch connection information from backend.") msg = _("Unable to fetch connection information from backend.")
raise webob.exc.HTTPInternalServerError(explanation=msg) raise webob.exc.HTTPInternalServerError(explanation=msg)

View File

@@ -492,6 +492,10 @@ class FailedCmdWithDump(VolumeDriverException):
message = _("Operation failed with status=%(status)s. Full dump: %(data)s") message = _("Operation failed with status=%(status)s. Full dump: %(data)s")
class InvalidConnectorException(VolumeDriverException):
message = _("Connector doesn't have required information: %(missing)s")
class GlanceMetadataExists(Invalid): class GlanceMetadataExists(Invalid):
message = _("Glance metadata cannot be updated, key %(key)s" message = _("Glance metadata cannot be updated, key %(key)s"
" exists for volume id %(volume_id)s") " exists for volume id %(volume_id)s")

View File

@@ -563,7 +563,7 @@ class AdminActionsTest(test.TestCase):
connector = {} connector = {}
# start service to handle rpc messages for attach requests # start service to handle rpc messages for attach requests
svc = self.start_service('volume', host='test') svc = self.start_service('volume', host='test')
self.assertRaises(exception.VolumeBackendAPIException, self.assertRaises(exception.InvalidInput,
self.volume_api.initialize_connection, self.volume_api.initialize_connection,
ctx, volume, connector) ctx, volume, connector)
# cleanup # cleanup

View File

@@ -129,7 +129,7 @@ class TestBaseISCSITargetDriver(test.TestCase):
def test_validate_connector(self): def test_validate_connector(self):
bad_connector = {'no_initiator': 'nada'} bad_connector = {'no_initiator': 'nada'}
self.assertRaises(exception.VolumeBackendAPIException, self.assertRaises(exception.InvalidConnectorException,
self.target.validate_connector, self.target.validate_connector,
bad_connector) bad_connector)

View File

@@ -1485,7 +1485,7 @@ class HuaweiTFCDriverTestCase(test.TestCase):
def test_validate_connector_failed(self): def test_validate_connector_failed(self):
invalid_connector = {'host': 'testhost'} invalid_connector = {'host': 'testhost'}
self.assertRaises(exception.VolumeBackendAPIException, self.assertRaises(exception.InvalidConnectorException,
self.driver.validate_connector, self.driver.validate_connector,
invalid_connector) invalid_connector)

View File

@@ -843,9 +843,9 @@ class FlashSystemDriverTestCase(test.TestCase):
self.driver._protocol = 'FC' self.driver._protocol = 'FC'
self.driver.validate_connector(conn_fc) self.driver.validate_connector(conn_fc)
self.driver.validate_connector(conn_both) self.driver.validate_connector(conn_both)
self.assertRaises(exception.VolumeDriverException, self.assertRaises(exception.InvalidConnectorException,
self.driver.validate_connector, conn_iscsi) self.driver.validate_connector, conn_iscsi)
self.assertRaises(exception.VolumeDriverException, self.assertRaises(exception.InvalidConnectorException,
self.driver.validate_connector, conn_neither) self.driver.validate_connector, conn_neither)
# clear environment # clear environment

View File

@@ -2042,24 +2042,24 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self.driver._state['enabled_protocols'] = set(['iSCSI']) self.driver._state['enabled_protocols'] = set(['iSCSI'])
self.driver.validate_connector(conn_iscsi) self.driver.validate_connector(conn_iscsi)
self.driver.validate_connector(conn_both) self.driver.validate_connector(conn_both)
self.assertRaises(exception.VolumeDriverException, self.assertRaises(exception.InvalidConnectorException,
self.driver.validate_connector, conn_fc) self.driver.validate_connector, conn_fc)
self.assertRaises(exception.VolumeDriverException, self.assertRaises(exception.InvalidConnectorException,
self.driver.validate_connector, conn_neither) self.driver.validate_connector, conn_neither)
self.driver._state['enabled_protocols'] = set(['FC']) self.driver._state['enabled_protocols'] = set(['FC'])
self.driver.validate_connector(conn_fc) self.driver.validate_connector(conn_fc)
self.driver.validate_connector(conn_both) self.driver.validate_connector(conn_both)
self.assertRaises(exception.VolumeDriverException, self.assertRaises(exception.InvalidConnectorException,
self.driver.validate_connector, conn_iscsi) self.driver.validate_connector, conn_iscsi)
self.assertRaises(exception.VolumeDriverException, self.assertRaises(exception.InvalidConnectorException,
self.driver.validate_connector, conn_neither) self.driver.validate_connector, conn_neither)
self.driver._state['enabled_protocols'] = set(['iSCSI', 'FC']) self.driver._state['enabled_protocols'] = set(['iSCSI', 'FC'])
self.driver.validate_connector(conn_iscsi) self.driver.validate_connector(conn_iscsi)
self.driver.validate_connector(conn_fc) self.driver.validate_connector(conn_fc)
self.driver.validate_connector(conn_both) self.driver.validate_connector(conn_both)
self.assertRaises(exception.VolumeDriverException, self.assertRaises(exception.InvalidConnectorException,
self.driver.validate_connector, conn_neither) self.driver.validate_connector, conn_neither)
def test_storwize_svc_host_maps(self): def test_storwize_svc_host_maps(self):

View File

@@ -4127,7 +4127,7 @@ class ISCSITestCase(DriverTestCase):
# Validate a connector without the initiator # Validate a connector without the initiator
connector = {'ip': '10.0.0.2', 'host': 'fakehost'} connector = {'ip': '10.0.0.2', 'host': 'fakehost'}
self.assertRaises(exception.VolumeBackendAPIException, self.assertRaises(exception.InvalidConnectorException,
iscsi_driver.validate_connector, connector) iscsi_driver.validate_connector, connector)
@@ -4213,27 +4213,27 @@ class FibreChannelTestCase(DriverTestCase):
def test_validate_connector_no_wwpns(self): def test_validate_connector_no_wwpns(self):
"""validate_connector() throws exception when it has no wwpns.""" """validate_connector() throws exception when it has no wwpns."""
connector = {'wwnns': ["not empty"]} connector = {'wwnns': ["not empty"]}
self.assertRaises(exception.VolumeDriverException, self.assertRaises(exception.InvalidConnectorException,
self.volume.driver.validate_connector, connector) self.volume.driver.validate_connector, connector)
def test_validate_connector_empty_wwpns(self): def test_validate_connector_empty_wwpns(self):
"""validate_connector() throws exception when it has empty wwpns.""" """validate_connector() throws exception when it has empty wwpns."""
connector = {'wwpns': [], connector = {'wwpns': [],
'wwnns': ["not empty"]} 'wwnns': ["not empty"]}
self.assertRaises(exception.VolumeDriverException, self.assertRaises(exception.InvalidConnectorException,
self.volume.driver.validate_connector, connector) self.volume.driver.validate_connector, connector)
def test_validate_connector_no_wwnns(self): def test_validate_connector_no_wwnns(self):
"""validate_connector() throws exception when it has no wwnns.""" """validate_connector() throws exception when it has no wwnns."""
connector = {'wwpns': ["not empty"]} connector = {'wwpns': ["not empty"]}
self.assertRaises(exception.VolumeDriverException, self.assertRaises(exception.InvalidConnectorException,
self.volume.driver.validate_connector, connector) self.volume.driver.validate_connector, connector)
def test_validate_connector_empty_wwnns(self): def test_validate_connector_empty_wwnns(self):
"""validate_connector() throws exception when it has empty wwnns.""" """validate_connector() throws exception when it has empty wwnns."""
connector = {'wwnns': [], connector = {'wwnns': [],
'wwpns': ["not empty"]} 'wwpns': ["not empty"]}
self.assertRaises(exception.VolumeDriverException, self.assertRaises(exception.InvalidConnectorException,
self.volume.driver.validate_connector, connector) self.volume.driver.validate_connector, connector)

View File

@@ -1079,11 +1079,12 @@ class ISCSIDriver(VolumeDriver):
def validate_connector(self, connector): def validate_connector(self, connector):
# iSCSI drivers require the initiator information # iSCSI drivers require the initiator information
if 'initiator' not in connector: required = 'initiator'
err_msg = (_('The volume driver requires the iSCSI initiator ' if required not in connector:
'name in the connector.')) err_msg = (_LE('The volume driver requires %(data)s '
LOG.error(err_msg) 'in the connector.'), {'data': required})
raise exception.VolumeBackendAPIException(data=err_msg) LOG.error(*err_msg)
raise exception.InvalidConnectorException(missing=required)
def terminate_connection(self, volume, connector, **kwargs): def terminate_connection(self, volume, connector, **kwargs):
pass pass
@@ -1361,8 +1362,9 @@ class FibreChannelDriver(VolumeDriver):
def validate_connector_has_setting(connector, setting): def validate_connector_has_setting(connector, setting):
"""Test for non-empty setting in connector.""" """Test for non-empty setting in connector."""
if setting not in connector or not connector[setting]: if setting not in connector or not connector[setting]:
msg = (_( msg = (_LE(
"FibreChannelDriver validate_connector failed. " "FibreChannelDriver validate_connector failed. "
"No '%s'. Make sure HBA state is Online.") % setting) "No '%(setting)s'. Make sure HBA state is Online."),
LOG.error(msg) {'setting': setting})
raise exception.VolumeDriverException(message=msg) LOG.error(*msg)
raise exception.InvalidConnectorException(missing=setting)

View File

@@ -21,7 +21,7 @@ import re
import time import time
from cinder import exception from cinder import exception
from cinder.i18n import _, _LW from cinder.i18n import _, _LE, _LW
from cinder.openstack.common import log as logging from cinder.openstack.common import log as logging
from cinder.volume import driver from cinder.volume import driver
from cinder.volume.drivers.huawei import huawei_utils from cinder.volume.drivers.huawei import huawei_utils
@@ -435,10 +435,10 @@ class HuaweiTFCDriver(driver.FibreChannelDriver):
def validate_connector(self, connector): def validate_connector(self, connector):
"""Check for wwpns in connector.""" """Check for wwpns in connector."""
if 'wwpns' not in connector: if 'wwpns' not in connector:
err_msg = (_('validate_connector: The FC driver requires the' err_msg = (_LE('validate_connector: The FC driver requires the'
' wwpns in the connector.')) ' wwpns in the connector.'))
LOG.error(err_msg) LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg) raise exception.InvalidConnectorException(missing='wwpns')
@fczm_utils.AddFCZone @fczm_utils.AddFCZone
def initialize_connection(self, volume, connector): def initialize_connection(self, volume, connector):

View File

@@ -1137,11 +1137,11 @@ class FlashSystemDriver(san.SanDriver):
def validate_connector(self, connector): def validate_connector(self, connector):
"""Check connector.""" """Check connector."""
if 'FC' != self._protocol or 'wwpns' not in connector: if 'FC' == self._protocol and 'wwpns' not in connector:
msg = (_('The connector does not contain the ' msg = (_LE('The connector does not contain the '
'required information.')) 'required information: wwpns is missing'))
LOG.error(msg) LOG.error(msg)
raise exception.VolumeDriverException(data=msg) raise exception.InvalidConnectorException(missing='wwpns')
def create_volume(self, volume): def create_volume(self, volume):
"""Create volume.""" """Create volume."""

View File

@@ -308,10 +308,11 @@ class StorwizeSVCDriver(san.SanDriver):
if 'FC' in self._state['enabled_protocols'] and 'wwpns' in connector: if 'FC' in self._state['enabled_protocols'] and 'wwpns' in connector:
valid = True valid = True
if not valid: if not valid:
msg = (_('The connector does not contain the required ' msg = (_LE('The connector does not contain the required '
'information.')) 'information.'))
LOG.error(msg) LOG.error(msg)
raise exception.VolumeDriverException(message=msg) raise exception.InvalidConnectorException(
missing='initiator or wwpns')
def _get_vdisk_params(self, type_id, volume_type=None, def _get_vdisk_params(self, type_id, volume_type=None,
volume_metadata=None): volume_metadata=None):

View File

@@ -886,8 +886,10 @@ class VolumeManager(manager.SchedulerDependentManager):
utils.require_driver_initialized(self.driver) utils.require_driver_initialized(self.driver)
try: try:
self.driver.validate_connector(connector) self.driver.validate_connector(connector)
except exception.InvalidConnectorException as err:
raise exception.InvalidInput(reason=err)
except Exception as err: except Exception as err:
err_msg = (_('Unable to fetch connection information from ' err_msg = (_('Unable to validate connector information in '
'backend: %(err)s') % {'err': err}) 'backend: %(err)s') % {'err': err})
LOG.error(err_msg) LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg) raise exception.VolumeBackendAPIException(data=err_msg)

View File

@@ -184,8 +184,8 @@ class ISCSITarget(driver.Target):
def validate_connector(self, connector): def validate_connector(self, connector):
# NOTE(jdg): api passes in connector which is initiator info # NOTE(jdg): api passes in connector which is initiator info
if 'initiator' not in connector: if 'initiator' not in connector:
err_msg = (_('The volume driver requires the iSCSI initiator ' err_msg = (_LE('The volume driver requires the iSCSI initiator '
'name in the connector.')) 'name in the connector.'))
LOG.error(err_msg) LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg) raise exception.InvalidConnectorException(missing='initiator')
return True return True