CONF = cfg.CONF
-class VolumeReplicationTestCase(test.TestCase):
+class VolumeReplicationTestCaseBase(test.TestCase):
def setUp(self):
- super(VolumeReplicationTestCase, self).setUp()
+ super(VolumeReplicationTestCaseBase, self).setUp()
self.ctxt = context.RequestContext('user', 'fake', False)
self.adm_ctxt = context.RequestContext('admin', 'fake', True)
self.manager = importutils.import_object(CONF.volume_manager)
spec=driver.VolumeDriver)
self.driver = self.driver_patcher.start()
+
+class VolumeReplicationTestCase(VolumeReplicationTestCaseBase):
@mock.patch('cinder.utils.require_driver_initialized')
def test_promote_replica_uninit_driver(self, _init):
"""Test promote replication when driver is not initialized."""
self.manager.reenable_replication,
self.adm_ctxt,
vol['id'])
+
+
+class VolumeManagerReplicationV2Tests(VolumeReplicationTestCaseBase):
+ mock_driver = None
+ mock_db = None
+ vol = None
+
+ def setUp(self):
+ super(VolumeManagerReplicationV2Tests, self).setUp()
+ self.mock_db = mock.Mock()
+ self.mock_driver = mock.Mock()
+ self.vol = test_utils.create_volume(self.ctxt,
+ status='available',
+ replication_status='enabling')
+ self.manager.driver = self.mock_driver
+ self.mock_driver.replication_enable.return_value = \
+ {'replication_status': 'enabled'}
+ self.mock_driver.replication_disable.return_value = \
+ {'replication_status': 'disabled'}
+ self.manager.db = self.mock_db
+ self.mock_db.volume_get.return_value = self.vol
+ self.mock_db.volume_update.return_value = self.vol
+
+ # enable_replication tests
+ @mock.patch('cinder.utils.require_driver_initialized')
+ def test_enable_replication_uninitialized_driver(self,
+ mock_require_driver_init):
+ mock_require_driver_init.side_effect = exception.DriverNotInitialized
+ self.assertRaises(exception.DriverNotInitialized,
+ self.manager.enable_replication,
+ self.ctxt,
+ self.vol)
+
+ def test_enable_replication_error_state(self):
+ self.vol['replication_status'] = 'error'
+ self.assertRaises(exception.InvalidVolume,
+ self.manager.enable_replication,
+ self.ctxt,
+ self.vol)
+
+ def test_enable_replication_driver_raises_cinder_exception(self):
+ self.mock_driver.replication_enable.side_effect = \
+ exception.CinderException
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.manager.enable_replication,
+ self.ctxt,
+ self.vol)
+ self.mock_db.volume_update.assert_called_with(
+ self.ctxt,
+ self.vol['id'],
+ {'replication_status': 'error'})
+
+ def test_enable_replication_driver_raises_exception(self):
+ self.mock_driver.replication_enable.side_effect = Exception
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.manager.enable_replication,
+ self.ctxt,
+ self.vol)
+ self.mock_db.volume_update.assert_called_with(
+ self.ctxt,
+ self.vol['id'],
+ {'replication_status': 'error'})
+
+ def test_enable_replication_success(self):
+ self.manager.enable_replication(self.ctxt, self.vol)
+ # volume_update is called multiple times
+ self.mock_db.volume_update.side_effect = [self.vol, self.vol]
+ self.mock_db.volume_update.assert_called_with(
+ self.ctxt,
+ self.vol['id'],
+ {'replication_status': 'enabled'})
+
+ # disable_replication tests
+ @mock.patch('cinder.utils.require_driver_initialized')
+ def test_disable_replication_uninitialized_driver(self,
+ mock_req_driver_init):
+ mock_req_driver_init.side_effect = exception.DriverNotInitialized
+ self.assertRaises(exception.DriverNotInitialized,
+ self.manager.disable_replication,
+ self.ctxt,
+ self.vol)
+
+ def test_disable_replication_error_state(self):
+ self.vol['replication_status'] = 'error'
+ self.assertRaises(exception.InvalidVolume,
+ self.manager.disable_replication,
+ self.ctxt,
+ self.vol)
+
+ def test_disable_replication_driver_raises_cinder_exception(self):
+ self.vol['replication_status'] = 'disabling'
+ self.mock_driver.replication_disable.side_effect = \
+ exception.CinderException
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.manager.disable_replication,
+ self.ctxt,
+ self.vol)
+ self.mock_db.volume_update.assert_called_with(
+ self.ctxt,
+ self.vol['id'],
+ {'replication_status': 'error'})
+
+ def test_disable_replication_driver_raises_exception(self):
+ self.vol['replication_status'] = 'disabling'
+ self.mock_driver.replication_disable.side_effect = Exception
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.manager.disable_replication,
+ self.ctxt,
+ self.vol)
+ self.mock_db.volume_update.assert_called_with(
+ self.ctxt,
+ self.vol['id'],
+ {'replication_status': 'error'})
+
+ def test_disable_replication_success(self):
+ self.vol['replication_status'] = 'disabling'
+ self.manager.disable_replication(self.ctxt, self.vol)
+ # volume_update is called multiple times
+ self.mock_db.volume_update.side_effect = [self.vol, self.vol]
+ self.mock_db.volume_update.assert_called_with(
+ self.ctxt,
+ self.vol['id'],
+ {'replication_status': 'disabled'})
+
+ # failover_replication tests
+ @mock.patch('cinder.utils.require_driver_initialized')
+ def test_failover_replication_uninitialized_driver(self,
+ mock_driver_init):
+ self.vol['replication_status'] = 'enabling_secondary'
+ # validate that driver is called even if uninitialized
+ mock_driver_init.side_effect = exception.DriverNotInitialized
+ self.manager.failover_replication(self.ctxt, self.vol)
+ # volume_update is called multiple times
+ self.mock_db.volume_update.side_effect = [self.vol, self.vol]
+ self.mock_db.volume_update.assert_called_with(
+ self.ctxt,
+ self.vol['id'],
+ {'replication_status': 'failed-over'})
+
+ def test_failover_replication_error_state(self):
+ self.vol['replication_status'] = 'error'
+ self.assertRaises(exception.InvalidVolume,
+ self.manager.failover_replication,
+ self.ctxt,
+ self.vol)
+
+ def test_failover_replication_driver_raises_cinder_exception(self):
+ self.vol['replication_status'] = 'enabling_secondary'
+ self.mock_driver.replication_failover.side_effect = \
+ exception.CinderException
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.manager.failover_replication,
+ self.ctxt,
+ self.vol)
+ self.mock_db.volume_update.assert_called_with(
+ self.ctxt,
+ self.vol['id'],
+ {'replication_status': 'error'})
+
+ def test_failover_replication_driver_raises_exception(self):
+ self.vol['replication_status'] = 'enabling_secondary'
+ self.mock_driver.replication_failover.side_effect = Exception
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.manager.failover_replication,
+ self.ctxt,
+ self.vol)
+ self.mock_db.volume_update.assert_called_with(
+ self.ctxt,
+ self.vol['id'],
+ {'replication_status': 'error'})
+
+ def test_failover_replication_success(self):
+ self.vol['replication_status'] = 'enabling_secondary'
+ self.manager.failover_replication(self.ctxt, self.vol)
+ # volume_update is called multiple times
+ self.mock_db.volume_update.side_effect = [self.vol, self.vol]
+ self.mock_db.volume_update.assert_called_with(
+ self.ctxt,
+ self.vol['id'],
+ {'replication_status': 'failed-over'})
The replication_driver_data response is vendor unique,
data returned/used by the driver. It is expected that
- the reponse from the driver is in the appropriate db update
+ the response from the driver is in the appropriate db update
format, in the form of a dict, where the vendor data is
stored under the key 'replication_driver_data'
"""Disable replication on the specified volume.
If the specified volume is currently replication enabled,
- this method can be used to disable the replciation process
+ this method can be used to disable the replication process
on the backend.
- Note that we still send this call to a driver whos volume
+ Note that we still send this call to a driver whose volume
may report replication-disabled already. We do this as a
safety mechanism to allow a driver to cleanup any mismatch
in state between Cinder and itself.
The replication_driver_data response is vendor unique,
data returned/used by the driver. It is expected that
- the reponse from the driver is in the appropriate db update
+ the response from the driver is in the appropriate db update
format, in the form of a dict, where the vendor data is
stored under the key 'replication_driver_data'
the replication target is a configured cinder backend, we'll
just update the host column for the volume.
- Very important point here is that in the case of a succesful
+ Very important point here is that in the case of a successful
failover, we want to update the replication_status of the
volume to "failed-over". This way there's an indication that
things worked as expected, and that it's evident that the volume
:param context: security context
:param volume: volume object returned by DB
:param secondary: Specifies rep target to fail over to
- :response: dict of udpates
+ :response: dict of updates
So the response would take the form:
{host: <properly formatted host string for db update>,
Example response for replicating to an unmanaged backend:
{'volume_id': volume['id'],
- 'targets':[{'type': 'managed',
+ 'targets':[{'type': 'unmanaged',
'vendor-key-1': 'value-1'}...]
NOTE: It's the responsibility of the driver to mask out any
except exception.CinderException:
err_msg = (_("Enable replication for volume failed."))
LOG.exception(err_msg, resource=volume)
+ self.db.volume_update(context,
+ volume['id'],
+ {'replication_status': 'error'})
raise exception.VolumeBackendAPIException(data=err_msg)
+ except Exception:
+ msg = _('enable_replication caused exception in driver.')
+ LOG.exception(msg, resource=volume)
+ self.db.volume_update(context,
+ volume['id'],
+ {'replication_status': 'error'})
+ raise exception.VolumeBackendAPIException(data=msg)
+
try:
if rep_driver_data:
volume = self.db.volume_update(context,
except exception.CinderException:
err_msg = (_("Disable replication for volume failed."))
LOG.exception(err_msg, resource=volume)
+ self.db.volume_update(context,
+ volume['id'],
+ {'replication_status': 'error'})
raise exception.VolumeBackendAPIException(data=err_msg)
+
+ except Exception:
+ msg = _('disable_replication caused exception in driver.')
+ LOG.exception(msg, resource=volume)
+ self.db.volume_update(context,
+ volume['id'],
+ {'replication_status': 'error'})
+ raise exception.VolumeBackendAPIException(msg)
+
try:
if rep_driver_data:
volume = self.db.volume_update(context,
except exception.CinderException as ex:
LOG.exception(_LE("Driver replication data update failed."),
resource=volume)
+ self.db.volume_update(context,
+ volume['id'],
+ {'replication_status': 'error'})
raise exception.VolumeBackendAPIException(reason=ex)
self.db.volume_update(context,
volume['id'],
# not being able to talk to the primary array that it's configured
# to manage.
+ if volume['replication_status'] != 'enabling_secondary':
+ msg = (_("Unable to failover replication due to invalid "
+ "replication status: %(status)s.") %
+ {'status': volume['replication_status']})
+ LOG.error(msg, resource=volume)
+ raise exception.InvalidVolume(reason=msg)
+
try:
volume = self.db.volume_get(context, volume['id'])
model_update = self.driver.replication_failover(context,
{'replication_status': 'error'})
raise exception.VolumeBackendAPIException(data=err_msg)
+ except Exception:
+ msg = _('replication_failover caused exception in driver.')
+ LOG.exception(msg, resource=volume)
+ self.db.volume_update(context,
+ volume['id'],
+ {'replication_status': 'error'})
+ raise exception.VolumeBackendAPIException(msg)
+
if model_update:
try:
volume = self.db.volume_update(
except exception.CinderException as ex:
LOG.exception(_LE("Driver replication data update failed."),
resource=volume)
+ self.db.volume_update(context,
+ volume['id'],
+ {'replication_status': 'error'})
raise exception.VolumeBackendAPIException(reason=ex)
# NOTE(jdg): We're setting replication status to failed-over
- # which indicates the volume is ok, things went as epected but
+ # which indicates the volume is ok, things went as expected but
# we're likely not replicating any longer because... well we
- # did a fail-over. In the case of admin brining primary
+ # did a fail-over. In the case of admin bringing primary
# back online he/she can use enable_replication to get this
# state set back to enabled.