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)
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)
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)
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)
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')
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)
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')