Exemplo n.º 1
0
def _check_microversion(context, url, microversion):
    """Checks to see if the requested microversion is supported by the current
    version of python-cinderclient and the volume API endpoint.

    :param context: The nova request context for auth.
    :param url: Cinder API endpoint URL.
    :param microversion: Requested microversion. If not available at the given
        API endpoint URL, a CinderAPIVersionNotAvailable exception is raised.
    :returns: The microversion if it is available. This can be used to
        construct the cinder v3 client object.
    :raises: CinderAPIVersionNotAvailable if the microversion is not available.
    """
    max_api_version = _get_highest_client_server_version(context, url)
    # Check if the max_api_version matches the requested minimum microversion.
    if max_api_version.matches(microversion):
        # The requested microversion is supported by the client and the server.
        return microversion
    raise exception.CinderAPIVersionNotAvailable(version=microversion)
Exemplo n.º 2
0
def _check_microversion(url, microversion):
    """Checks to see if the requested microversion is supported by the current
    version of python-cinderclient and the volume API endpoint.

    :param url: Cinder API endpoint URL.
    :param microversion: Requested microversion. If not available at the given
        API endpoint URL, a CinderAPIVersionNotAvailable exception is raised.
    :returns: The microversion if it is available. This can be used to
        construct the cinder v3 client object.
    :raises: CinderAPIVersionNotAvailable if the microversion is not available.
    """
    max_api_version = cinder_client.get_highest_client_server_version(url)
    # get_highest_client_server_version returns a float which we need to cast
    # to a str and create an APIVersion object to do our version comparison.
    max_api_version = cinder_api_versions.APIVersion(str(max_api_version))
    # Check if the max_api_version matches the requested minimum microversion.
    if max_api_version.matches(microversion):
        # The requested microversion is supported by the client and the server.
        return microversion
    raise exception.CinderAPIVersionNotAvailable(version=microversion)
Exemplo n.º 3
0
class TestUpgradeCheckCinderAPI(test.NoDBTestCase):

    def setUp(self):
        super(TestUpgradeCheckCinderAPI, self).setUp()
        self.cmd = status.UpgradeCommands()

    def test_cinder_not_configured(self):
        self.flags(auth_type=None, group='cinder')
        self.assertEqual(upgradecheck.Code.SUCCESS,
                         self.cmd._check_cinder().code)

    @mock.patch('nova.volume.cinder.is_microversion_supported',
                side_effect=exception.CinderAPIVersionNotAvailable(
                    version='3.44'))
    def test_microversion_not_available(self, mock_version_check):
        self.flags(auth_type='password', group='cinder')
        result = self.cmd._check_cinder()
        mock_version_check.assert_called_once()
        self.assertEqual(upgradecheck.Code.FAILURE, result.code)
        self.assertIn('Cinder API 3.44 or greater is required.',
                      result.details)

    @mock.patch('nova.volume.cinder.is_microversion_supported',
                side_effect=test.TestingException('oops'))
    def test_unknown_error(self, mock_version_check):
        self.flags(auth_type='password', group='cinder')
        result = self.cmd._check_cinder()
        mock_version_check.assert_called_once()
        self.assertEqual(upgradecheck.Code.WARNING, result.code)
        self.assertIn('oops', result.details)

    @mock.patch('nova.volume.cinder.is_microversion_supported')
    def test_microversion_available(self, mock_version_check):
        self.flags(auth_type='password', group='cinder')
        result = self.cmd._check_cinder()
        mock_version_check.assert_called_once()
        self.assertEqual(upgradecheck.Code.SUCCESS, result.code)
Exemplo n.º 4
0
class CrossCellMigrationTaskTestCase(test.NoDBTestCase):
    def setUp(self):
        super(CrossCellMigrationTaskTestCase, self).setUp()
        source_context = nova_context.get_context()
        host_selection = objects.Selection(cell_uuid=uuids.cell_uuid)
        migration = objects.Migration(id=1, cross_cell_move=False)
        self.task = cross_cell_migrate.CrossCellMigrationTask(
            source_context, mock.sentinel.instance, mock.sentinel.flavor,
            mock.sentinel.request_spec, migration,
            mock.sentinel.compute_rpcapi, host_selection,
            mock.sentinel.alternate_hosts)

    def test_execute_and_rollback(self):
        """Basic test to just hit execute and rollback."""
        # Mock out the things that execute calls
        with test.nested(
                mock.patch.object(self.task.migration, 'save'),
                mock.patch.object(self.task, '_perform_external_api_checks'),
                mock.patch.object(self.task, '_setup_target_cell_db'),
        ) as (
                mock_migration_save,
                mock_perform_external_api_checks,
                mock_setup_target_cell_db,
        ):
            self.task.execute()
        # Assert the calls
        self.assertTrue(self.task.migration.cross_cell_move,
                        'Migration.cross_cell_move should be True.')
        mock_migration_save.assert_called_once_with()
        mock_perform_external_api_checks.assert_called_once_with()
        mock_setup_target_cell_db.assert_called_once_with()
        # Now rollback the completed sub-tasks
        self.task.rollback()

    @mock.patch('nova.volume.cinder.is_microversion_supported',
                return_value=None)
    def test_perform_external_api_checks_ok(self, mock_cinder_mv_check):
        """Tests the happy path scenario where both cinder and neutron APIs
        are new enough for what we need.
        """
        with mock.patch.object(self.task.network_api,
                               'supports_port_binding_extension',
                               return_value=True) as mock_neutron_check:
            self.task._perform_external_api_checks()
        mock_neutron_check.assert_called_once_with(self.task.context)
        mock_cinder_mv_check.assert_called_once_with(self.task.context, '3.44')

    @mock.patch('nova.volume.cinder.is_microversion_supported',
                return_value=None)
    def test_perform_external_api_checks_old_neutron(self,
                                                     mock_cinder_mv_check):
        """Tests the case that neutron API is old."""
        with mock.patch.object(self.task.network_api,
                               'supports_port_binding_extension',
                               return_value=False):
            ex = self.assertRaises(exception.MigrationPreCheckError,
                                   self.task._perform_external_api_checks)
            self.assertIn('Required networking service API extension',
                          six.text_type(ex))

    @mock.patch(
        'nova.volume.cinder.is_microversion_supported',
        side_effect=exception.CinderAPIVersionNotAvailable(version='3.44'))
    def test_perform_external_api_checks_old_cinder(self,
                                                    mock_cinder_mv_check):
        """Tests the case that cinder API is old."""
        with mock.patch.object(self.task.network_api,
                               'supports_port_binding_extension',
                               return_value=True):
            ex = self.assertRaises(exception.MigrationPreCheckError,
                                   self.task._perform_external_api_checks)
            self.assertIn('Cinder API version', six.text_type(ex))

    @mock.patch('nova.conductor.tasks.cross_cell_migrate.LOG.exception')
    def test_rollback_idempotent(self, mock_log_exception):
        """Tests that the rollback routine hits all completed tasks even if
        one or more of them fail their own rollback routine.
        """
        # Mock out some completed tasks
        for x in range(3):
            task = mock.Mock()
            # The 2nd task will fail its rollback.
            if x == 1:
                task.rollback.side_effect = test.TestingException('sub-task')
            self.task._completed_tasks[str(x)] = task
        # Run execute but mock _execute to fail somehow.
        with mock.patch.object(self.task,
                               '_execute',
                               side_effect=test.TestingException('main task')):
            # The TestingException from the main task should be raised.
            ex = self.assertRaises(test.TestingException, self.task.execute)
            self.assertEqual('main task', six.text_type(ex))
        # And all three sub-task rollbacks should have been called.
        for subtask in self.task._completed_tasks.values():
            subtask.rollback.assert_called_once_with()
        # The 2nd task rollback should have raised and been logged.
        mock_log_exception.assert_called_once()
        self.assertEqual('1', mock_log_exception.call_args[0][1])

    @mock.patch('nova.objects.CellMapping.get_by_uuid')
    @mock.patch('nova.context.set_target_cell')
    @mock.patch.object(cross_cell_migrate.TargetDBSetupTask, 'execute')
    def test_setup_target_cell_db(self, mock_target_db_set_task_execute,
                                  mock_set_target_cell, mock_get_cell_mapping):
        """Tests setting up and executing TargetDBSetupTask"""
        mock_target_db_set_task_execute.return_value = (
            mock.sentinel.target_cell_instance,
            mock.sentinel.target_cell_migration)
        result = self.task._setup_target_cell_db()
        mock_target_db_set_task_execute.assert_called_once_with()
        mock_get_cell_mapping.assert_called_once_with(
            self.task.context, self.task.host_selection.cell_uuid)
        # The target_cell_context should be set on the main task but as a copy
        # of the source context.
        self.assertIsNotNone(self.task._target_cell_context)
        self.assertIsNot(self.task._target_cell_context, self.task.context)
        # The target cell context should have been targeted to the target
        # cell mapping.
        mock_set_target_cell.assert_called_once_with(
            self.task._target_cell_context, mock_get_cell_mapping.return_value)
        # The resulting migration record from TargetDBSetupTask should have
        # been returned.
        self.assertIs(result, mock.sentinel.target_cell_migration)
        # The target_cell_instance should be set on the main task.
        self.assertIsNotNone(self.task._target_cell_instance)
        self.assertIs(self.task._target_cell_instance,
                      mock.sentinel.target_cell_instance)
        # And the completed task should have been recorded for rollbacks.
        self.assertIn('TargetDBSetupTask', self.task._completed_tasks)
        self.assertIsInstance(self.task._completed_tasks['TargetDBSetupTask'],
                              cross_cell_migrate.TargetDBSetupTask)
Exemplo n.º 5
0
class CinderApiTestCase(test.NoDBTestCase):
    def setUp(self):
        super(CinderApiTestCase, self).setUp()

        self.api = cinder.API()
        self.ctx = context.get_admin_context()

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get(self, mock_cinderclient):
        volume_id = 'volume_id1'
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.get(self.ctx, volume_id)

        mock_cinderclient.assert_called_once_with(self.ctx, microversion=None)
        mock_volumes.get.assert_called_once_with(volume_id)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_failed_notfound(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.get.side_effect = (
            cinder_exception.NotFound(404, '404'))

        self.assertRaises(exception.VolumeNotFound, self.api.get, self.ctx,
                          'id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_failed_badrequest(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.get.side_effect = (
            cinder_exception.BadRequest(400, '400'))

        self.assertRaises(exception.InvalidInput, self.api.get, self.ctx,
                          'id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_failed_connection_failed(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.get.side_effect = (
            cinder_exception.ConnectionError(''))

        self.assertRaises(exception.CinderConnectionFailed, self.api.get,
                          self.ctx, 'id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_with_shared_targets(self, mock_cinderclient):
        """Tests getting a volume at microversion 3.48 which includes the
        shared_targets and service_uuid parameters in the volume response body.
        """
        mock_volume = mock.MagicMock(shared_targets=False,
                                     service_uuid=uuids.service_uuid)
        mock_volumes = mock.MagicMock()
        mock_volumes.get.return_value = mock_volume
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)
        vol = self.api.get(self.ctx, uuids.volume_id, microversion='3.48')
        mock_cinderclient.assert_called_once_with(self.ctx,
                                                  microversion='3.48')
        mock_volumes.get.assert_called_once_with(uuids.volume_id)
        self.assertIn('shared_targets', vol)
        self.assertFalse(vol['shared_targets'])
        self.assertEqual(uuids.service_uuid, vol['service_uuid'])

    @mock.patch(
        'nova.volume.cinder.cinderclient',
        side_effect=exception.CinderAPIVersionNotAvailable(version='3.48'))
    def test_get_microversion_not_supported(self, mock_cinderclient):
        """Tests getting a volume at microversion 3.48 but that version
        is not available.
        """
        self.assertRaises(exception.CinderAPIVersionNotAvailable,
                          self.api.get,
                          self.ctx,
                          uuids.volume_id,
                          microversion='3.48')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_create(self, mock_cinderclient):
        volume = FakeVolume('id1')
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)
        mock_volumes.create.return_value = volume

        created_volume = self.api.create(self.ctx, 1, '', '')
        self.assertEqual('id1', created_volume['id'])
        self.assertEqual(1, created_volume['size'])

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.create.assert_called_once_with(1,
                                                    availability_zone=None,
                                                    description='',
                                                    imageRef=None,
                                                    metadata=None,
                                                    name='',
                                                    project_id=None,
                                                    snapshot_id=None,
                                                    user_id=None,
                                                    volume_type=None)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_create_failed(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.create.side_effect = (
            cinder_exception.BadRequest(400, '400'))

        self.assertRaises(exception.InvalidInput, self.api.create, self.ctx, 1,
                          '', '')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_create_over_quota_failed(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.create.side_effect = (
            cinder_exception.OverLimit(413))
        self.assertRaises(exception.OverQuota, self.api.create, self.ctx, 1,
                          '', '')
        mock_cinderclient.return_value.volumes.create.assert_called_once_with(
            1,
            user_id=None,
            imageRef=None,
            availability_zone=None,
            volume_type=None,
            description='',
            snapshot_id=None,
            name='',
            project_id=None,
            metadata=None)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_all(self, mock_cinderclient):
        volume1 = FakeVolume('id1')
        volume2 = FakeVolume('id2')

        volume_list = [volume1, volume2]
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)
        mock_volumes.list.return_value = volume_list

        volumes = self.api.get_all(self.ctx)
        self.assertEqual(2, len(volumes))
        self.assertEqual(['id1', 'id2'], [vol['id'] for vol in volumes])

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.list.assert_called_once_with(detailed=True,
                                                  search_opts={})

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_all_with_search(self, mock_cinderclient):
        volume1 = FakeVolume('id1')

        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)
        mock_volumes.list.return_value = [volume1]

        volumes = self.api.get_all(self.ctx, search_opts={'id': 'id1'})
        self.assertEqual(1, len(volumes))
        self.assertEqual('id1', volumes[0]['id'])

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.list.assert_called_once_with(detailed=True,
                                                  search_opts={'id': 'id1'})

    @mock.patch.object(cinder.az,
                       'get_instance_availability_zone',
                       return_value='zone1')
    def test_check_availability_zone_differs(self, mock_get_instance_az):
        self.flags(cross_az_attach=False, group='cinder')
        volume = {
            'id': uuids.volume_id,
            'status': 'available',
            'attach_status': 'detached',
            'availability_zone': 'zone2'
        }
        instance = fake_instance_obj(self.ctx)
        # Simulate _provision_instances in the compute API; the instance is not
        # created in the API so the instance will not have an id attribute set.
        delattr(instance, 'id')

        self.assertRaises(exception.InvalidVolume,
                          self.api.check_availability_zone, self.ctx, volume,
                          instance)
        mock_get_instance_az.assert_called_once_with(self.ctx, instance)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_reserve_volume(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.reserve_volume(self.ctx, 'id1')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.reserve.assert_called_once_with('id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_unreserve_volume(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.unreserve_volume(self.ctx, 'id1')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.unreserve.assert_called_once_with('id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_begin_detaching(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.begin_detaching(self.ctx, 'id1')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.begin_detaching.assert_called_once_with('id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_roll_detaching(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.roll_detaching(self.ctx, 'id1')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.roll_detaching.assert_called_once_with('id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attach(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.attach(self.ctx, 'id1', 'uuid', 'point')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.attach.assert_called_once_with('id1',
                                                    'uuid',
                                                    'point',
                                                    mode='rw')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attach_with_mode(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.attach(self.ctx, 'id1', 'uuid', 'point', mode='ro')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.attach.assert_called_once_with('id1',
                                                    'uuid',
                                                    'point',
                                                    mode='ro')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_create(self, mock_cinderclient):
        """Tests the happy path for creating a volume attachment without a
        mountpoint.
        """
        attachment_ref = {'id': uuids.attachment_id, 'connection_info': {}}
        expected_attachment_ref = {
            'id': uuids.attachment_id,
            'connection_info': {}
        }
        mock_cinderclient.return_value.attachments.create.return_value = (
            attachment_ref)
        result = self.api.attachment_create(self.ctx, uuids.volume_id,
                                            uuids.instance_id)
        self.assertEqual(expected_attachment_ref, result)
        mock_cinderclient.return_value.attachments.create.\
            assert_called_once_with(uuids.volume_id, None, uuids.instance_id)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_create_with_mountpoint(self, mock_cinderclient):
        """Tests the happy path for creating a volume attachment with a
        mountpoint.
        """
        attachment_ref = {'id': uuids.attachment_id, 'connection_info': {}}
        expected_attachment_ref = {
            'id': uuids.attachment_id,
            'connection_info': {}
        }
        mock_cinderclient.return_value.attachments.create.return_value = (
            attachment_ref)
        original_connector = {'host': 'fake-host'}
        updated_connector = dict(original_connector, mountpoint='/dev/vdb')
        result = self.api.attachment_create(self.ctx,
                                            uuids.volume_id,
                                            uuids.instance_id,
                                            connector=original_connector,
                                            mountpoint='/dev/vdb')
        self.assertEqual(expected_attachment_ref, result)
        # Make sure the original connector wasn't modified.
        self.assertNotIn('mountpoint', original_connector)
        # Make sure the mountpoint was passed through via the connector.
        mock_cinderclient.return_value.attachments.create.\
            assert_called_once_with(uuids.volume_id, updated_connector,
                                    uuids.instance_id)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_create_volume_not_found(self, mock_cinderclient):
        """Tests that the translate_volume_exception decorator is used."""
        # fake out the volume not found error
        mock_cinderclient.return_value.attachments.create.side_effect = (
            cinder_exception.NotFound(404))
        self.assertRaises(exception.VolumeNotFound, self.api.attachment_create,
                          self.ctx, uuids.volume_id, uuids.instance_id)

    @mock.patch(
        'nova.volume.cinder.cinderclient',
        side_effect=exception.CinderAPIVersionNotAvailable(version='3.44'))
    def test_attachment_create_unsupported_api_version(self,
                                                       mock_cinderclient):
        """Tests that CinderAPIVersionNotAvailable is passed back through
        if 3.44 isn't available.
        """
        self.assertRaises(exception.CinderAPIVersionNotAvailable,
                          self.api.attachment_create, self.ctx,
                          uuids.volume_id, uuids.instance_id)
        mock_cinderclient.assert_called_once_with(self.ctx, '3.44')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_update(self, mock_cinderclient):
        """Tests the happy path for updating a volume attachment without
        a mountpoint.
        """
        fake_attachment = FakeAttachment()
        connector = {'host': 'fake-host'}
        expected_attachment_ref = {
            'id': uuids.attachment_id,
            'volume_id': fake_attachment.volume_id,
            'connection_info': {
                'attach_mode': 'rw',
                'attached_at': fake_attachment.attached_at,
                'data': {
                    'foo': 'bar',
                    'target_lun': '1'
                },
                'detached_at': None,
                'driver_volume_type': 'fake_type',
                'instance': fake_attachment.instance,
                'status': 'attaching',
                'volume_id': fake_attachment.volume_id
            }
        }
        mock_cinderclient.return_value.attachments.update.return_value = (
            fake_attachment)
        result = self.api.attachment_update(self.ctx,
                                            uuids.attachment_id,
                                            connector=connector)
        self.assertEqual(expected_attachment_ref, result)
        # Make sure the connector wasn't modified.
        self.assertNotIn('mountpoint', connector)
        mock_cinderclient.return_value.attachments.update.\
            assert_called_once_with(uuids.attachment_id, connector)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_update_with_mountpoint(self, mock_cinderclient):
        """Tests the happy path for updating a volume attachment with
        a mountpoint.
        """
        fake_attachment = FakeAttachment()
        original_connector = {'host': 'fake-host'}
        updated_connector = dict(original_connector, mountpoint='/dev/vdb')
        expected_attachment_ref = {
            'id': uuids.attachment_id,
            'volume_id': fake_attachment.volume_id,
            'connection_info': {
                'attach_mode': 'rw',
                'attached_at': fake_attachment.attached_at,
                'data': {
                    'foo': 'bar',
                    'target_lun': '1'
                },
                'detached_at': None,
                'driver_volume_type': 'fake_type',
                'instance': fake_attachment.instance,
                'status': 'attaching',
                'volume_id': fake_attachment.volume_id
            }
        }
        mock_cinderclient.return_value.attachments.update.return_value = (
            fake_attachment)
        result = self.api.attachment_update(self.ctx,
                                            uuids.attachment_id,
                                            connector=original_connector,
                                            mountpoint='/dev/vdb')
        self.assertEqual(expected_attachment_ref, result)
        # Make sure the original connector wasn't modified.
        self.assertNotIn('mountpoint', original_connector)
        # Make sure the mountpoint was passed through via the connector.
        mock_cinderclient.return_value.attachments.update.\
            assert_called_once_with(uuids.attachment_id, updated_connector)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_update_attachment_not_found(self, mock_cinderclient):
        """Tests that the translate_attachment_exception decorator is used."""
        # fake out the volume not found error
        mock_cinderclient.return_value.attachments.update.side_effect = (
            cinder_exception.NotFound(404))
        self.assertRaises(exception.VolumeAttachmentNotFound,
                          self.api.attachment_update,
                          self.ctx,
                          uuids.attachment_id,
                          connector={'host': 'fake-host'})

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_update_attachment_no_connector(self,
                                                       mock_cinderclient):
        """Tests that the translate_cinder_exception decorator is used."""
        # fake out the volume bad request error
        mock_cinderclient.return_value.attachments.update.side_effect = (
            cinder_exception.BadRequest(400))
        self.assertRaises(exception.InvalidInput,
                          self.api.attachment_update,
                          self.ctx,
                          uuids.attachment_id,
                          connector=None)

    @mock.patch(
        'nova.volume.cinder.cinderclient',
        side_effect=exception.CinderAPIVersionNotAvailable(version='3.44'))
    def test_attachment_update_unsupported_api_version(self,
                                                       mock_cinderclient):
        """Tests that CinderAPIVersionNotAvailable is passed back through
        if 3.44 isn't available.
        """
        self.assertRaises(exception.CinderAPIVersionNotAvailable,
                          self.api.attachment_update,
                          self.ctx,
                          uuids.attachment_id,
                          connector={})
        mock_cinderclient.assert_called_once_with(self.ctx,
                                                  '3.44',
                                                  skip_version_check=True)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_delete(self, mock_cinderclient):
        mock_attachments = mock.MagicMock()
        mock_cinderclient.return_value = \
            mock.MagicMock(attachments=mock_attachments)

        attachment_id = uuids.attachment
        self.api.attachment_delete(self.ctx, attachment_id)

        mock_cinderclient.assert_called_once_with(self.ctx,
                                                  '3.44',
                                                  skip_version_check=True)
        mock_attachments.delete.assert_called_once_with(attachment_id)

    @mock.patch('nova.volume.cinder.LOG')
    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_delete_failed(self, mock_cinderclient, mock_log):
        mock_cinderclient.return_value.attachments.delete.side_effect = (
            cinder_exception.NotFound(404, '404'))

        attachment_id = uuids.attachment
        ex = self.assertRaises(exception.VolumeAttachmentNotFound,
                               self.api.attachment_delete, self.ctx,
                               attachment_id)

        self.assertEqual(404, ex.code)
        self.assertIn(attachment_id, six.text_type(ex))

    @mock.patch(
        'nova.volume.cinder.cinderclient',
        side_effect=exception.CinderAPIVersionNotAvailable(version='3.44'))
    def test_attachment_delete_unsupported_api_version(self,
                                                       mock_cinderclient):
        """Tests that CinderAPIVersionNotAvailable is passed back through
        if 3.44 isn't available.
        """
        self.assertRaises(exception.CinderAPIVersionNotAvailable,
                          self.api.attachment_delete, self.ctx,
                          uuids.attachment_id)
        mock_cinderclient.assert_called_once_with(self.ctx,
                                                  '3.44',
                                                  skip_version_check=True)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_complete(self, mock_cinderclient):
        mock_attachments = mock.MagicMock()
        mock_cinderclient.return_value = \
            mock.MagicMock(attachments=mock_attachments)

        attachment_id = uuids.attachment
        self.api.attachment_complete(self.ctx, attachment_id)

        mock_cinderclient.assert_called_once_with(self.ctx,
                                                  '3.44',
                                                  skip_version_check=True)
        mock_attachments.complete.assert_called_once_with(attachment_id)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_complete_failed(self, mock_cinderclient):
        mock_cinderclient.return_value.attachments.complete.side_effect = (
            cinder_exception.NotFound(404, '404'))

        attachment_id = uuids.attachment
        ex = self.assertRaises(exception.VolumeAttachmentNotFound,
                               self.api.attachment_complete, self.ctx,
                               attachment_id)

        self.assertEqual(404, ex.code)
        self.assertIn(attachment_id, six.text_type(ex))

    @mock.patch(
        'nova.volume.cinder.cinderclient',
        side_effect=exception.CinderAPIVersionNotAvailable(version='3.44'))
    def test_attachment_complete_unsupported_api_version(
            self, mock_cinderclient):
        """Tests that CinderAPIVersionNotAvailable is passed back.

        If microversion 3.44 isn't available that should result in a
        CinderAPIVersionNotAvailable exception.
        """
        self.assertRaises(exception.CinderAPIVersionNotAvailable,
                          self.api.attachment_complete, self.ctx,
                          uuids.attachment_id)
        mock_cinderclient.assert_called_once_with(self.ctx,
                                                  '3.44',
                                                  skip_version_check=True)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_detach(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(version='2',
                                                        volumes=mock_volumes)

        self.api.detach(self.ctx,
                        'id1',
                        instance_uuid='fake_uuid',
                        attachment_id='fakeid')

        mock_cinderclient.assert_called_with(self.ctx)
        mock_volumes.detach.assert_called_once_with('id1', 'fakeid')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_detach_no_attachment_id(self, mock_cinderclient):
        attachment = {'server_id': 'fake_uuid', 'attachment_id': 'fakeid'}

        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(version='2',
                                                        volumes=mock_volumes)
        mock_cinderclient.return_value.volumes.get.return_value = \
            FakeVolume('id1', attachments=[attachment])

        self.api.detach(self.ctx, 'id1', instance_uuid='fake_uuid')

        mock_cinderclient.assert_called_with(self.ctx, microversion=None)
        mock_volumes.detach.assert_called_once_with('id1', None)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_detach_no_attachment_id_multiattach(self, mock_cinderclient):
        attachment = {'server_id': 'fake_uuid', 'attachment_id': 'fakeid'}

        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(version='2',
                                                        volumes=mock_volumes)
        mock_cinderclient.return_value.volumes.get.return_value = \
            FakeVolume('id1', attachments=[attachment], multiattach=True)

        self.api.detach(self.ctx, 'id1', instance_uuid='fake_uuid')

        mock_cinderclient.assert_called_with(self.ctx, microversion=None)
        mock_volumes.detach.assert_called_once_with('id1', 'fakeid')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_get(self, mock_cinderclient):
        mock_attachment = mock.MagicMock()
        mock_cinderclient.return_value = \
            mock.MagicMock(attachments=mock_attachment)

        attachment_id = uuids.attachment
        self.api.attachment_get(self.ctx, attachment_id)

        mock_cinderclient.assert_called_once_with(self.ctx,
                                                  '3.44',
                                                  skip_version_check=True)
        mock_attachment.show.assert_called_once_with(attachment_id)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_get_failed(self, mock_cinderclient):
        mock_cinderclient.return_value.attachments.show.side_effect = (
            cinder_exception.NotFound(404, '404'))

        attachment_id = uuids.attachment
        ex = self.assertRaises(exception.VolumeAttachmentNotFound,
                               self.api.attachment_get, self.ctx,
                               attachment_id)

        self.assertEqual(404, ex.code)
        self.assertIn(attachment_id, six.text_type(ex))

    @mock.patch(
        'nova.volume.cinder.cinderclient',
        side_effect=exception.CinderAPIVersionNotAvailable(version='3.44'))
    def test_attachment_get_unsupported_api_version(self, mock_cinderclient):
        """Tests that CinderAPIVersionNotAvailable is passed back.

        If microversion 3.44 isn't available that should result in a
        CinderAPIVersionNotAvailable exception.
        """
        self.assertRaises(exception.CinderAPIVersionNotAvailable,
                          self.api.attachment_get, self.ctx,
                          uuids.attachment_id)
        mock_cinderclient.assert_called_once_with(self.ctx,
                                                  '3.44',
                                                  skip_version_check=True)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_initialize_connection(self, mock_cinderclient):
        connection_info = {'foo': 'bar'}
        mock_cinderclient.return_value.volumes. \
            initialize_connection.return_value = connection_info

        volume_id = 'fake_vid'
        connector = {'host': 'fakehost1'}
        actual = self.api.initialize_connection(self.ctx, volume_id, connector)

        expected = connection_info
        expected['connector'] = connector
        self.assertEqual(expected, actual)

        mock_cinderclient.return_value.volumes. \
            initialize_connection.assert_called_once_with(volume_id, connector)

    @mock.patch('nova.volume.cinder.LOG')
    @mock.patch('nova.volume.cinder.cinderclient')
    def test_initialize_connection_exception_no_code(self, mock_cinderclient,
                                                     mock_log):
        mock_cinderclient.return_value.volumes. \
            initialize_connection.side_effect = (
                cinder_exception.ClientException(500, "500"))
        mock_cinderclient.return_value.volumes. \
            terminate_connection.side_effect = (
                test.TestingException)

        connector = {'host': 'fakehost1'}
        self.assertRaises(cinder_exception.ClientException,
                          self.api.initialize_connection, self.ctx, 'id1',
                          connector)
        self.assertIsNone(mock_log.error.call_args_list[1][0][1]['code'])

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_initialize_connection_rollback(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.\
            initialize_connection.side_effect = (
                cinder_exception.ClientException(500, "500"))

        connector = {'host': 'host1'}
        ex = self.assertRaises(cinder_exception.ClientException,
                               self.api.initialize_connection, self.ctx, 'id1',
                               connector)
        self.assertEqual(500, ex.code)
        mock_cinderclient.return_value.volumes.\
            terminate_connection.assert_called_once_with('id1', connector)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_initialize_connection_no_rollback(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.\
            initialize_connection.side_effect = test.TestingException

        connector = {'host': 'host1'}
        self.assertRaises(test.TestingException,
                          self.api.initialize_connection, self.ctx, 'id1',
                          connector)
        self.assertFalse(
            mock_cinderclient.return_value.volumes.terminate_connection.called)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_terminate_connection(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.terminate_connection(self.ctx, 'id1', 'connector')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.terminate_connection.assert_called_once_with(
            'id1', 'connector')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_delete(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.delete(self.ctx, 'id1')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.delete.assert_called_once_with('id1')

    def test_update(self):
        self.assertRaises(NotImplementedError, self.api.update, self.ctx, '',
                          '')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_absolute_limits_forbidden(self, cinderclient):
        """Tests to make sure we gracefully handle a Forbidden error raised
        from python-cinderclient when getting limits.
        """
        cinderclient.return_value.limits.get.side_effect = (
            cinder_exception.Forbidden(403))
        self.assertRaises(exception.Forbidden, self.api.get_absolute_limits,
                          self.ctx)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_absolute_limits(self, cinderclient):
        """Tests the happy path of getting the absolute limits."""
        expected_limits = {
            "totalSnapshotsUsed": 0,
            "maxTotalBackups": 10,
            "maxTotalVolumeGigabytes": 1000,
            "maxTotalSnapshots": 10,
            "maxTotalBackupGigabytes": 1000,
            "totalBackupGigabytesUsed": 0,
            "maxTotalVolumes": 10,
            "totalVolumesUsed": 0,
            "totalBackupsUsed": 0,
            "totalGigabytesUsed": 0
        }
        limits_obj = cinder_limits.Limits(None, {'absolute': expected_limits})
        cinderclient.return_value.limits.get.return_value = limits_obj
        actual_limits = self.api.get_absolute_limits(self.ctx)
        self.assertDictEqual(expected_limits, actual_limits)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_snapshot(self, mock_cinderclient):
        snapshot_id = 'snapshot_id'
        mock_volume_snapshots = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(
            volume_snapshots=mock_volume_snapshots)

        self.api.get_snapshot(self.ctx, snapshot_id)

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volume_snapshots.get.assert_called_once_with(snapshot_id)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_snapshot_failed_notfound(self, mock_cinderclient):
        mock_cinderclient.return_value.volume_snapshots.get.side_effect = (
            cinder_exception.NotFound(404, '404'))

        self.assertRaises(exception.SnapshotNotFound, self.api.get_snapshot,
                          self.ctx, 'snapshot_id')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_snapshot_connection_failed(self, mock_cinderclient):
        mock_cinderclient.return_value.volume_snapshots.get.side_effect = (
            cinder_exception.ConnectionError(''))

        self.assertRaises(exception.CinderConnectionFailed,
                          self.api.get_snapshot, self.ctx, 'snapshot_id')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_all_snapshots(self, mock_cinderclient):
        snapshot1 = FakeSnapshot('snapshot_id1', 'id1')
        snapshot2 = FakeSnapshot('snapshot_id2', 'id2')

        snapshot_list = [snapshot1, snapshot2]
        mock_volume_snapshots = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(
            volume_snapshots=mock_volume_snapshots)
        mock_volume_snapshots.list.return_value = snapshot_list

        snapshots = self.api.get_all_snapshots(self.ctx)
        self.assertEqual(2, len(snapshots))
        self.assertEqual(['snapshot_id1', 'snapshot_id2'],
                         [snap['id'] for snap in snapshots])
        self.assertEqual(['id1', 'id2'],
                         [snap['volume_id'] for snap in snapshots])

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volume_snapshots.list.assert_called_once_with(detailed=True)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_create_snapshot(self, mock_cinderclient):
        snapshot = FakeSnapshot('snapshot_id1', 'id1')
        mock_volume_snapshots = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(
            volume_snapshots=mock_volume_snapshots)
        mock_volume_snapshots.create.return_value = snapshot

        created_snapshot = self.api.create_snapshot(self.ctx, 'id1', 'name',
                                                    'description')

        self.assertEqual('snapshot_id1', created_snapshot['id'])
        self.assertEqual('id1', created_snapshot['volume_id'])
        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volume_snapshots.create.assert_called_once_with(
            'id1', False, 'name', 'description')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_create_force(self, mock_cinderclient):
        snapshot = FakeSnapshot('snapshot_id1', 'id1')
        mock_volume_snapshots = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(
            volume_snapshots=mock_volume_snapshots)
        mock_volume_snapshots.create.return_value = snapshot

        created_snapshot = self.api.create_snapshot_force(
            self.ctx, 'id1', 'name', 'description')

        self.assertEqual('snapshot_id1', created_snapshot['id'])
        self.assertEqual('id1', created_snapshot['volume_id'])
        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volume_snapshots.create.assert_called_once_with(
            'id1', True, 'name', 'description')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_delete_snapshot(self, mock_cinderclient):
        mock_volume_snapshots = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(
            volume_snapshots=mock_volume_snapshots)

        self.api.delete_snapshot(self.ctx, 'snapshot_id')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volume_snapshots.delete.assert_called_once_with('snapshot_id')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_update_snapshot_status(self, mock_cinderclient):
        mock_volume_snapshots = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(
            volume_snapshots=mock_volume_snapshots)

        self.api.update_snapshot_status(self.ctx, 'snapshot_id', 'error')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volume_snapshots.update_snapshot_status.assert_called_once_with(
            'snapshot_id', {
                'status': 'error',
                'progress': '90%'
            })

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_volume_encryption_metadata(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.get_volume_encryption_metadata(
            self.ctx, {'encryption_key_id': 'fake_key'})

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.get_encryption_metadata.assert_called_once_with(
            {'encryption_key_id': 'fake_key'})

    def test_translate_cinder_exception_no_error(self):
        my_func = mock.Mock()
        my_func.__name__ = 'my_func'
        my_func.return_value = 'foo'

        res = cinder.translate_cinder_exception(my_func)('fizzbuzz', 'bar',
                                                         'baz')

        self.assertEqual('foo', res)
        my_func.assert_called_once_with('fizzbuzz', 'bar', 'baz')

    def test_translate_cinder_exception_cinder_connection_error(self):
        self._do_translate_cinder_exception_test(
            cinder_exception.ConnectionError, exception.CinderConnectionFailed)

    def test_translate_cinder_exception_keystone_connection_error(self):
        self._do_translate_cinder_exception_test(
            keystone_exception.ConnectionError,
            exception.CinderConnectionFailed)

    def test_translate_cinder_exception_cinder_bad_request(self):
        self._do_translate_cinder_exception_test(
            cinder_exception.BadRequest(400, '400'), exception.InvalidInput)

    def test_translate_cinder_exception_keystone_bad_request(self):
        self._do_translate_cinder_exception_test(keystone_exception.BadRequest,
                                                 exception.InvalidInput)

    def test_translate_cinder_exception_cinder_forbidden(self):
        self._do_translate_cinder_exception_test(
            cinder_exception.Forbidden(403, '403'), exception.Forbidden)

    def test_translate_cinder_exception_keystone_forbidden(self):
        self._do_translate_cinder_exception_test(keystone_exception.Forbidden,
                                                 exception.Forbidden)

    def test_translate_mixed_exception_over_limit(self):
        self._do_translate_mixed_exception_test(cinder_exception.OverLimit(''),
                                                exception.OverQuota)

    def test_translate_mixed_exception_volume_not_found(self):
        self._do_translate_mixed_exception_test(cinder_exception.NotFound(''),
                                                exception.VolumeNotFound)

    def test_translate_mixed_exception_keystone_not_found(self):
        self._do_translate_mixed_exception_test(keystone_exception.NotFound,
                                                exception.VolumeNotFound)

    def _do_translate_cinder_exception_test(self, raised_exc, expected_exc):
        self._do_translate_exception_test(raised_exc, expected_exc,
                                          cinder.translate_cinder_exception)

    def _do_translate_mixed_exception_test(self, raised_exc, expected_exc):
        self._do_translate_exception_test(raised_exc, expected_exc,
                                          cinder.translate_mixed_exceptions)

    def _do_translate_exception_test(self, raised_exc, expected_exc, wrapper):
        my_func = mock.Mock()
        my_func.__name__ = 'my_func'
        my_func.side_effect = raised_exc

        self.assertRaises(expected_exc, wrapper(my_func), 'foo', 'bar', 'baz')
Exemplo n.º 6
0
def cinderclient(context, microversion=None, skip_version_check=False):
    """Constructs a cinder client object for making API requests.

    :param context: The nova request context for auth.
    :param microversion: Optional microversion to check against the client.
        This implies that Cinder v3 is required for any calls that require a
        microversion. If the microversion is not available, this method will
        raise an CinderAPIVersionNotAvailable exception.
    :param skip_version_check: If True and a specific microversion is
        requested, the version discovery check is skipped and the microversion
        is used directly. This should only be used if a previous check for the
        same microversion was successful.
    """
    global _SESSION

    if not _SESSION:
        _SESSION = ks_loading.load_session_from_conf_options(
            CONF, nova.conf.cinder.cinder_group.name)

    url = None
    endpoint_override = None

    auth = service_auth.get_auth_plugin(context)
    service_type, service_name, interface = CONF.cinder.catalog_info.split(':')

    service_parameters = {'service_type': service_type,
                          'service_name': service_name,
                          'interface': interface,
                          'region_name': CONF.cinder.os_region_name}

    if CONF.cinder.endpoint_template:
        url = CONF.cinder.endpoint_template % context.to_dict()
        endpoint_override = url
    else:
        url = _SESSION.get_endpoint(auth, **service_parameters)

    # TODO(jamielennox): This should be using proper version discovery from
    # the cinder service rather than just inspecting the URL for certain string
    # values.
    version = cinder_client.get_volume_api_from_url(url)

    if version == '1':
        raise exception.UnsupportedCinderAPIVersion(version=version)

    if version == '2':
        if microversion is not None:
            # The Cinder v2 API does not support microversions.
            raise exception.CinderAPIVersionNotAvailable(version=microversion)
        LOG.warning("The support for the Cinder API v2 is deprecated, please "
                    "upgrade to Cinder API v3.")

    if version == '3':
        version = '3.0'
        # Check to see a specific microversion is requested and if so, can it
        # be handled by the backing server.
        if microversion is not None:
            if skip_version_check:
                version = microversion
            else:
                version = _check_microversion(url, microversion)

    return cinder_client.Client(version,
                                session=_SESSION,
                                auth=auth,
                                endpoint_override=endpoint_override,
                                connect_retries=CONF.cinder.http_retries,
                                global_request_id=context.global_id,
                                **service_parameters)
Exemplo n.º 7
0
class CinderApiTestCase(test.NoDBTestCase):
    def setUp(self):
        super(CinderApiTestCase, self).setUp()

        self.api = cinder.API()
        self.ctx = context.get_admin_context()

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get(self, mock_cinderclient):
        volume_id = 'volume_id1'
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.get(self.ctx, volume_id)

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.get.assert_called_once_with(volume_id)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_failed_notfound(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.get.side_effect = (
            cinder_exception.NotFound(404, '404'))

        self.assertRaises(exception.VolumeNotFound, self.api.get, self.ctx,
                          'id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_failed_badrequest(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.get.side_effect = (
            cinder_exception.BadRequest(400, '400'))

        self.assertRaises(exception.InvalidInput, self.api.get, self.ctx,
                          'id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_failed_connection_failed(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.get.side_effect = (
            cinder_exception.ConnectionError(''))

        self.assertRaises(exception.CinderConnectionFailed, self.api.get,
                          self.ctx, 'id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_create(self, mock_cinderclient):
        volume = FakeVolume('id1')
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)
        mock_volumes.create.return_value = volume

        created_volume = self.api.create(self.ctx, 1, '', '')
        self.assertEqual('id1', created_volume['id'])
        self.assertEqual(1, created_volume['size'])

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.create.assert_called_once_with(1,
                                                    availability_zone=None,
                                                    description='',
                                                    imageRef=None,
                                                    metadata=None,
                                                    name='',
                                                    project_id=None,
                                                    snapshot_id=None,
                                                    user_id=None,
                                                    volume_type=None)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_create_failed(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.create.side_effect = (
            cinder_exception.BadRequest(400, '400'))

        self.assertRaises(exception.InvalidInput, self.api.create, self.ctx, 1,
                          '', '')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_create_over_quota_failed(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.create.side_effect = (
            cinder_exception.OverLimit(413))
        self.assertRaises(exception.OverQuota, self.api.create, self.ctx, 1,
                          '', '')
        mock_cinderclient.return_value.volumes.create.assert_called_once_with(
            1,
            user_id=None,
            imageRef=None,
            availability_zone=None,
            volume_type=None,
            description='',
            snapshot_id=None,
            name='',
            project_id=None,
            metadata=None)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_all(self, mock_cinderclient):
        volume1 = FakeVolume('id1')
        volume2 = FakeVolume('id2')

        volume_list = [volume1, volume2]
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)
        mock_volumes.list.return_value = volume_list

        volumes = self.api.get_all(self.ctx)
        self.assertEqual(2, len(volumes))
        self.assertEqual(['id1', 'id2'], [vol['id'] for vol in volumes])

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.list.assert_called_once_with(detailed=True,
                                                  search_opts={})

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_all_with_search(self, mock_cinderclient):
        volume1 = FakeVolume('id1')

        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)
        mock_volumes.list.return_value = [volume1]

        volumes = self.api.get_all(self.ctx, search_opts={'id': 'id1'})
        self.assertEqual(1, len(volumes))
        self.assertEqual('id1', volumes[0]['id'])

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.list.assert_called_once_with(detailed=True,
                                                  search_opts={'id': 'id1'})

    @mock.patch.object(cinder.az,
                       'get_instance_availability_zone',
                       return_value='zone1')
    def test_check_availability_zone_differs(self, mock_get_instance_az):
        self.flags(cross_az_attach=False, group='cinder')
        volume = {
            'id': uuids.volume_id,
            'status': 'available',
            'attach_status': 'detached',
            'availability_zone': 'zone2'
        }
        instance = fake_instance_obj(self.ctx)
        # Simulate _provision_instances in the compute API; the instance is not
        # created in the API so the instance will not have an id attribute set.
        delattr(instance, 'id')

        self.assertRaises(exception.InvalidVolume,
                          self.api.check_availability_zone, self.ctx, volume,
                          instance)
        mock_get_instance_az.assert_called_once_with(self.ctx, instance)

    def test_check_detach(self):
        volume = {
            'id': 'fake',
            'status': 'in-use',
            'attach_status': 'attached',
            'attachments': {
                uuids.instance: {
                    'attachment_id': uuids.attachment
                }
            }
        }
        self.assertIsNone(self.api.check_detach(self.ctx, volume))
        instance = fake_instance_obj(self.ctx)
        instance.uuid = uuids.instance
        self.assertIsNone(self.api.check_detach(self.ctx, volume, instance))
        instance.uuid = uuids.instance2
        self.assertRaises(exception.VolumeUnattached, self.api.check_detach,
                          self.ctx, volume, instance)
        volume['attachments'] = {}
        self.assertRaises(exception.VolumeUnattached, self.api.check_detach,
                          self.ctx, volume, instance)
        volume['status'] = 'available'
        self.assertRaises(exception.InvalidVolume, self.api.check_detach,
                          self.ctx, volume)
        volume['attach_status'] = 'detached'
        self.assertRaises(exception.InvalidVolume, self.api.check_detach,
                          self.ctx, volume)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_reserve_volume(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.reserve_volume(self.ctx, 'id1')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.reserve.assert_called_once_with('id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_unreserve_volume(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.unreserve_volume(self.ctx, 'id1')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.unreserve.assert_called_once_with('id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_begin_detaching(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.begin_detaching(self.ctx, 'id1')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.begin_detaching.assert_called_once_with('id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_roll_detaching(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.roll_detaching(self.ctx, 'id1')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.roll_detaching.assert_called_once_with('id1')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attach(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.attach(self.ctx, 'id1', 'uuid', 'point')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.attach.assert_called_once_with('id1',
                                                    'uuid',
                                                    'point',
                                                    mode='rw')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attach_with_mode(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.attach(self.ctx, 'id1', 'uuid', 'point', mode='ro')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.attach.assert_called_once_with('id1',
                                                    'uuid',
                                                    'point',
                                                    mode='ro')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_create(self, mock_cinderclient):
        """Tests the happy path for creating a volume attachment."""
        values = {
            'id': uuids.attachment_id,
            'status': 'reserved',
            'instance': uuids.instance_id,
            'volume_id': uuids.volume_id,
            'attached_at': timeutils.utcnow(),
            'detached_at': None,
            'attach_mode': 'rw',
            'connection_info': None
        }
        fake_attachment = mock.Mock(
            autospec='cinderclient.v3.attachments.VolumeAttachment', **values)
        mock_cinderclient.return_value.attachments.create.return_value = (
            fake_attachment)
        result = self.api.attachment_create(self.ctx, uuids.volume_id,
                                            uuids.instance_id)
        self.assertEqual(fake_attachment, result)
        mock_cinderclient.return_value.attachments.create.\
            assert_called_once_with(uuids.volume_id, None, uuids.instance_id)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_create_volume_not_found(self, mock_cinderclient):
        """Tests that the translate_volume_exception decorator is used."""
        # fake out the volume not found error
        mock_cinderclient.return_value.attachments.create.side_effect = (
            cinder_exception.NotFound(404))
        self.assertRaises(exception.VolumeNotFound, self.api.attachment_create,
                          self.ctx, uuids.volume_id, uuids.instance_id)

    @mock.patch(
        'nova.volume.cinder.cinderclient',
        side_effect=exception.CinderAPIVersionNotAvailable(version='3.27'))
    def test_attachment_create_unsupported_api_version(self,
                                                       mock_cinderclient):
        """Tests that CinderAPIVersionNotAvailable is passed back through
        if 3.27 isn't available.
        """
        self.assertRaises(exception.CinderAPIVersionNotAvailable,
                          self.api.attachment_create, self.ctx,
                          uuids.volume_id, uuids.instance_id)
        mock_cinderclient.assert_called_once_with(self.ctx, '3.27')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_update(self, mock_cinderclient):
        """Tests the happy path for updating a volume attachment."""
        values = {
            'id': uuids.attachment_id,
            'status': 'attached',
            'instance': uuids.instance_id,
            'volume_id': uuids.volume_id,
            'attached_at': timeutils.utcnow(),
            'detached_at': None,
            'attach_mode': 'rw',
            'connection_info': {
                'data': {
                    'foo': 'bar'
                }
            }
        }
        fake_attachment = mock.Mock(
            autospec='cinderclient.v3.attachments.VolumeAttachment', **values)
        mock_cinderclient.return_value.attachments.update.return_value = (
            fake_attachment)
        result = self.api.attachment_update(self.ctx,
                                            uuids.attachment_id,
                                            connector={'host': 'fake-host'})
        self.assertEqual(fake_attachment, result)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_update_attachment_not_found(self, mock_cinderclient):
        """Tests that the translate_attachment_exception decorator is used."""
        # fake out the volume not found error
        mock_cinderclient.return_value.attachments.update.side_effect = (
            cinder_exception.NotFound(404))
        self.assertRaises(exception.VolumeAttachmentNotFound,
                          self.api.attachment_update,
                          self.ctx,
                          uuids.attachment_id,
                          connector={'host': 'fake-host'})

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_update_attachment_no_connector(self,
                                                       mock_cinderclient):
        """Tests that the translate_cinder_exception decorator is used."""
        # fake out the volume bad request error
        mock_cinderclient.return_value.attachments.update.side_effect = (
            cinder_exception.BadRequest(400))
        self.assertRaises(exception.InvalidInput,
                          self.api.attachment_update,
                          self.ctx,
                          uuids.attachment_id,
                          connector=None)

    @mock.patch(
        'nova.volume.cinder.cinderclient',
        side_effect=exception.CinderAPIVersionNotAvailable(version='3.27'))
    def test_attachment_update_unsupported_api_version(self,
                                                       mock_cinderclient):
        """Tests that CinderAPIVersionNotAvailable is passed back through
        if 3.27 isn't available.
        """
        self.assertRaises(exception.CinderAPIVersionNotAvailable,
                          self.api.attachment_update,
                          self.ctx,
                          uuids.attachment_id,
                          connector={})
        mock_cinderclient.assert_called_once_with(self.ctx,
                                                  '3.27',
                                                  skip_version_check=True)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_delete(self, mock_cinderclient):
        mock_attachments = mock.MagicMock()
        mock_cinderclient.return_value = \
            mock.MagicMock(attachments=mock_attachments)

        attachment_id = uuids.attachment
        self.api.attachment_delete(self.ctx, attachment_id)

        mock_cinderclient.assert_called_once_with(self.ctx,
                                                  '3.27',
                                                  skip_version_check=True)
        mock_attachments.delete.assert_called_once_with(attachment_id)

    @mock.patch('nova.volume.cinder.LOG')
    @mock.patch('nova.volume.cinder.cinderclient')
    def test_attachment_delete_failed(self, mock_cinderclient, mock_log):
        mock_cinderclient.return_value.attachments.delete.side_effect = (
            cinder_exception.NotFound(404, '404'))

        attachment_id = uuids.attachment
        ex = self.assertRaises(exception.VolumeAttachmentNotFound,
                               self.api.attachment_delete, self.ctx,
                               attachment_id)

        self.assertEqual(404, ex.code)
        self.assertIn(attachment_id, six.text_type(ex))

    @mock.patch(
        'nova.volume.cinder.cinderclient',
        side_effect=exception.CinderAPIVersionNotAvailable(version='3.27'))
    def test_attachment_delete_unsupported_api_version(self,
                                                       mock_cinderclient):
        """Tests that CinderAPIVersionNotAvailable is passed back through
        if 3.27 isn't available.
        """
        self.assertRaises(exception.CinderAPIVersionNotAvailable,
                          self.api.attachment_delete, self.ctx,
                          uuids.attachment_id)
        mock_cinderclient.assert_called_once_with(self.ctx,
                                                  '3.27',
                                                  skip_version_check=True)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_detach(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(version='2',
                                                        volumes=mock_volumes)

        self.api.detach(self.ctx,
                        'id1',
                        instance_uuid='fake_uuid',
                        attachment_id='fakeid')

        mock_cinderclient.assert_called_with(self.ctx)
        mock_volumes.detach.assert_called_once_with('id1', 'fakeid')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_detach_no_attachment_id(self, mock_cinderclient):
        attachment = {'server_id': 'fake_uuid', 'attachment_id': 'fakeid'}

        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(version='2',
                                                        volumes=mock_volumes)
        mock_cinderclient.return_value.volumes.get.return_value = \
            FakeVolume('id1', attachments=[attachment])

        self.api.detach(self.ctx, 'id1', instance_uuid='fake_uuid')

        mock_cinderclient.assert_called_with(self.ctx)
        mock_volumes.detach.assert_called_once_with('id1', None)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_detach_no_attachment_id_multiattach(self, mock_cinderclient):
        attachment = {'server_id': 'fake_uuid', 'attachment_id': 'fakeid'}

        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(version='2',
                                                        volumes=mock_volumes)
        mock_cinderclient.return_value.volumes.get.return_value = \
            FakeVolume('id1', attachments=[attachment], multiattach=True)

        self.api.detach(self.ctx, 'id1', instance_uuid='fake_uuid')

        mock_cinderclient.assert_called_with(self.ctx)
        mock_volumes.detach.assert_called_once_with('id1', 'fakeid')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_initialize_connection(self, mock_cinderclient):
        connection_info = {'foo': 'bar'}
        mock_cinderclient.return_value.volumes. \
            initialize_connection.return_value = connection_info

        volume_id = 'fake_vid'
        connector = {'host': 'fakehost1'}
        actual = self.api.initialize_connection(self.ctx, volume_id, connector)

        expected = connection_info
        expected['connector'] = connector
        self.assertEqual(expected, actual)

        mock_cinderclient.return_value.volumes. \
            initialize_connection.assert_called_once_with(volume_id, connector)

    @mock.patch('nova.volume.cinder.LOG')
    @mock.patch('nova.volume.cinder.cinderclient')
    def test_initialize_connection_exception_no_code(self, mock_cinderclient,
                                                     mock_log):
        mock_cinderclient.return_value.volumes. \
            initialize_connection.side_effect = (
                cinder_exception.ClientException(500, "500"))
        mock_cinderclient.return_value.volumes. \
            terminate_connection.side_effect = (
                test.TestingException)

        connector = {'host': 'fakehost1'}
        self.assertRaises(cinder_exception.ClientException,
                          self.api.initialize_connection, self.ctx, 'id1',
                          connector)
        self.assertIsNone(mock_log.error.call_args_list[1][0][1]['code'])

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_initialize_connection_rollback(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.\
            initialize_connection.side_effect = (
                cinder_exception.ClientException(500, "500"))

        connector = {'host': 'host1'}
        ex = self.assertRaises(cinder_exception.ClientException,
                               self.api.initialize_connection, self.ctx, 'id1',
                               connector)
        self.assertEqual(500, ex.code)
        mock_cinderclient.return_value.volumes.\
            terminate_connection.assert_called_once_with('id1', connector)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_initialize_connection_no_rollback(self, mock_cinderclient):
        mock_cinderclient.return_value.volumes.\
            initialize_connection.side_effect = test.TestingException

        connector = {'host': 'host1'}
        self.assertRaises(test.TestingException,
                          self.api.initialize_connection, self.ctx, 'id1',
                          connector)
        self.assertFalse(
            mock_cinderclient.return_value.volumes.terminate_connection.called)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_terminate_connection(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.terminate_connection(self.ctx, 'id1', 'connector')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.terminate_connection.assert_called_once_with(
            'id1', 'connector')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_delete(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.delete(self.ctx, 'id1')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.delete.assert_called_once_with('id1')

    def test_update(self):
        self.assertRaises(NotImplementedError, self.api.update, self.ctx, '',
                          '')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_snapshot(self, mock_cinderclient):
        snapshot_id = 'snapshot_id'
        mock_volume_snapshots = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(
            volume_snapshots=mock_volume_snapshots)

        self.api.get_snapshot(self.ctx, snapshot_id)

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volume_snapshots.get.assert_called_once_with(snapshot_id)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_snapshot_failed_notfound(self, mock_cinderclient):
        mock_cinderclient.return_value.volume_snapshots.get.side_effect = (
            cinder_exception.NotFound(404, '404'))

        self.assertRaises(exception.SnapshotNotFound, self.api.get_snapshot,
                          self.ctx, 'snapshot_id')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_snapshot_connection_failed(self, mock_cinderclient):
        mock_cinderclient.return_value.volume_snapshots.get.side_effect = (
            cinder_exception.ConnectionError(''))

        self.assertRaises(exception.CinderConnectionFailed,
                          self.api.get_snapshot, self.ctx, 'snapshot_id')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_all_snapshots(self, mock_cinderclient):
        snapshot1 = FakeSnapshot('snapshot_id1', 'id1')
        snapshot2 = FakeSnapshot('snapshot_id2', 'id2')

        snapshot_list = [snapshot1, snapshot2]
        mock_volume_snapshots = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(
            volume_snapshots=mock_volume_snapshots)
        mock_volume_snapshots.list.return_value = snapshot_list

        snapshots = self.api.get_all_snapshots(self.ctx)
        self.assertEqual(2, len(snapshots))
        self.assertEqual(['snapshot_id1', 'snapshot_id2'],
                         [snap['id'] for snap in snapshots])
        self.assertEqual(['id1', 'id2'],
                         [snap['volume_id'] for snap in snapshots])

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volume_snapshots.list.assert_called_once_with(detailed=True)

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_create_snapshot(self, mock_cinderclient):
        snapshot = FakeSnapshot('snapshot_id1', 'id1')
        mock_volume_snapshots = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(
            volume_snapshots=mock_volume_snapshots)
        mock_volume_snapshots.create.return_value = snapshot

        created_snapshot = self.api.create_snapshot(self.ctx, 'id1', 'name',
                                                    'description')

        self.assertEqual('snapshot_id1', created_snapshot['id'])
        self.assertEqual('id1', created_snapshot['volume_id'])
        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volume_snapshots.create.assert_called_once_with(
            'id1', False, 'name', 'description')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_create_force(self, mock_cinderclient):
        snapshot = FakeSnapshot('snapshot_id1', 'id1')
        mock_volume_snapshots = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(
            volume_snapshots=mock_volume_snapshots)
        mock_volume_snapshots.create.return_value = snapshot

        created_snapshot = self.api.create_snapshot_force(
            self.ctx, 'id1', 'name', 'description')

        self.assertEqual('snapshot_id1', created_snapshot['id'])
        self.assertEqual('id1', created_snapshot['volume_id'])
        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volume_snapshots.create.assert_called_once_with(
            'id1', True, 'name', 'description')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_delete_snapshot(self, mock_cinderclient):
        mock_volume_snapshots = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(
            volume_snapshots=mock_volume_snapshots)

        self.api.delete_snapshot(self.ctx, 'snapshot_id')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volume_snapshots.delete.assert_called_once_with('snapshot_id')

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_update_snapshot_status(self, mock_cinderclient):
        mock_volume_snapshots = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(
            volume_snapshots=mock_volume_snapshots)

        self.api.update_snapshot_status(self.ctx, 'snapshot_id', 'error')

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volume_snapshots.update_snapshot_status.assert_called_once_with(
            'snapshot_id', {
                'status': 'error',
                'progress': '90%'
            })

    @mock.patch('nova.volume.cinder.cinderclient')
    def test_get_volume_encryption_metadata(self, mock_cinderclient):
        mock_volumes = mock.MagicMock()
        mock_cinderclient.return_value = mock.MagicMock(volumes=mock_volumes)

        self.api.get_volume_encryption_metadata(
            self.ctx, {'encryption_key_id': 'fake_key'})

        mock_cinderclient.assert_called_once_with(self.ctx)
        mock_volumes.get_encryption_metadata.assert_called_once_with(
            {'encryption_key_id': 'fake_key'})

    def test_translate_cinder_exception_no_error(self):
        my_func = mock.Mock()
        my_func.__name__ = 'my_func'
        my_func.return_value = 'foo'

        res = cinder.translate_cinder_exception(my_func)('fizzbuzz', 'bar',
                                                         'baz')

        self.assertEqual('foo', res)
        my_func.assert_called_once_with('fizzbuzz', 'bar', 'baz')

    def test_translate_cinder_exception_cinder_connection_error(self):
        self._do_translate_cinder_exception_test(
            cinder_exception.ConnectionError, exception.CinderConnectionFailed)

    def test_translate_cinder_exception_keystone_connection_error(self):
        self._do_translate_cinder_exception_test(
            keystone_exception.ConnectionError,
            exception.CinderConnectionFailed)

    def test_translate_cinder_exception_cinder_bad_request(self):
        self._do_translate_cinder_exception_test(
            cinder_exception.BadRequest(400, '400'), exception.InvalidInput)

    def test_translate_cinder_exception_keystone_bad_request(self):
        self._do_translate_cinder_exception_test(keystone_exception.BadRequest,
                                                 exception.InvalidInput)

    def test_translate_cinder_exception_cinder_forbidden(self):
        self._do_translate_cinder_exception_test(
            cinder_exception.Forbidden(403, '403'), exception.Forbidden)

    def test_translate_cinder_exception_keystone_forbidden(self):
        self._do_translate_cinder_exception_test(keystone_exception.Forbidden,
                                                 exception.Forbidden)

    def test_translate_mixed_exception_over_limit(self):
        self._do_translate_mixed_exception_test(cinder_exception.OverLimit(''),
                                                exception.OverQuota)

    def test_translate_mixed_exception_volume_not_found(self):
        self._do_translate_mixed_exception_test(cinder_exception.NotFound(''),
                                                exception.VolumeNotFound)

    def test_translate_mixed_exception_keystone_not_found(self):
        self._do_translate_mixed_exception_test(keystone_exception.NotFound,
                                                exception.VolumeNotFound)

    def _do_translate_cinder_exception_test(self, raised_exc, expected_exc):
        self._do_translate_exception_test(raised_exc, expected_exc,
                                          cinder.translate_cinder_exception)

    def _do_translate_mixed_exception_test(self, raised_exc, expected_exc):
        self._do_translate_exception_test(raised_exc, expected_exc,
                                          cinder.translate_mixed_exceptions)

    def _do_translate_exception_test(self, raised_exc, expected_exc, wrapper):
        my_func = mock.Mock()
        my_func.__name__ = 'my_func'
        my_func.side_effect = raised_exc

        self.assertRaises(expected_exc, wrapper(my_func), 'foo', 'bar', 'baz')