def test_list_volume_with_count_param_version_not_matched(self, action): self._create_multiple_volumes_with_different_project() is_detail = True if 'detail' in action else False req = fakes.HTTPRequest.blank("/v3/%s?with_count=True" % action) req.headers = mv.get_mv_header( mv.get_prior_version(mv.SUPPORT_COUNT_INFO)) req.api_version_request = mv.get_api_version( mv.get_prior_version(mv.SUPPORT_COUNT_INFO)) ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True) req.environ['cinder.context'] = ctxt res_dict = self.controller._get_volumes(req, is_detail=is_detail) self.assertNotIn('count', res_dict)
def test_get_manageable_snapshots_detail_previous_version(self): res = self._get_resp_get('fakehost', True, True, version=mv.get_prior_version( mv.MANAGE_EXISTING_LIST)) self.assertEqual(http_client.NOT_FOUND, res.status_int)
class VolumeTransferController(volume_transfer_v2.VolumeTransferController): """The transfer API controller for the OpenStack API V3.""" @wsgi.response(http_client.ACCEPTED) @validation.schema(volume_transfer.create, mv.BASE_VERSION, mv.get_prior_version(mv.TRANSFER_WITH_SNAPSHOTS)) @validation.schema(volume_transfer.create_v355, mv.TRANSFER_WITH_SNAPSHOTS) def create(self, req, body): """Create a new volume transfer.""" LOG.debug('Creating new volume transfer %s', body) context = req.environ['cinder.context'] transfer = body['transfer'] volume_id = transfer['volume_id'] name = transfer.get('name', None) if name is not None: name = name.strip() no_snapshots = strutils.bool_from_string( transfer.get('no_snapshots', False)) LOG.info("Creating transfer of volume %s", volume_id) try: new_transfer = self.transfer_api.create(context, volume_id, name, no_snapshots=no_snapshots) # Not found exception will be handled at the wsgi level except exception.Invalid as error: raise exc.HTTPBadRequest(explanation=error.msg) transfer = self._view_builder.create(req, dict(new_transfer)) return transfer
def test_volume_types_index_with_extra_specs(self): req = fakes.HTTPRequest.blank( '/v3/%s/types?extra_specs={"key1":"value1"}' % fake.PROJECT_ID, use_admin_context=False) req.api_version_request = mv.get_api_version(mv.get_prior_version( mv.SUPPORT_VOLUME_TYPE_FILTER)) res_dict = self.controller.index(req) # since __DEFAULT__ type always exists, total number of volume types # is total_types_created + 1. In this case it's 4 self.assertEqual(4, len(res_dict['volume_types'])) # Test filter volume type with extra specs req = fakes.HTTPRequest.blank( '/v3/%s/types?extra_specs={"key1":"value1"}' % fake.PROJECT_ID, use_admin_context=True) req.api_version_request = mv.get_api_version( mv.SUPPORT_VOLUME_TYPE_FILTER) res_dict = self.controller.index(req) self.assertEqual(1, len(res_dict['volume_types'])) self.assertDictEqual({'key1': 'value1', 'RESKEY:availability_zones': 'az1,az2'}, res_dict['volume_types'][0]['extra_specs']) # Test filter volume type with 'availability_zones' req = fakes.HTTPRequest.blank( '/v3/%s/types?extra_specs={"RESKEY:availability_zones":"az1"}' % fake.PROJECT_ID, use_admin_context=True) req.api_version_request = mv.get_api_version( mv.SUPPORT_VOLUME_TYPE_FILTER) res_dict = self.controller.index(req) self.assertEqual(2, len(res_dict['volume_types'])) self.assertEqual( ['volume_type1', 'volume_type2'], sorted([az['name'] for az in res_dict['volume_types']]))
def test_get_manageable_volumes_detail_previous_version(self): res = self._get_resp_get('fakehost', True, False, version=mv.get_prior_version( mv.MANAGE_EXISTING_LIST)) self.assertEqual(HTTPStatus.NOT_FOUND, res.status_int)
def test_snapshot_create_force_false(self, force_flag, mock_create): snapshot_name = 'Snapshot Test Name' snapshot_description = 'Snapshot Test Desc' snapshot = { "volume_id": fake.VOLUME_ID, "force": force_flag, "name": snapshot_name, "description": snapshot_description } body = dict(snapshot=snapshot) req = create_snapshot_query_with_metadata('{"key2": "val2"}', mv.SNAPSHOT_IN_USE) self.assertRaises(exc.HTTPBadRequest, self.controller.create, req, body=body) mock_create.assert_not_called() # prevent regression -- shouldn't raise for pre-mv-3.66 req = create_snapshot_query_with_metadata( '{"key2": "val2"}', mv.get_prior_version(mv.SNAPSHOT_IN_USE)) self.controller.create(req, body=body) # ... but also shouldn't allow an in-use snapshot self.assertNotIn('allow_in_use', mock_create.call_args_list[0][1])
def test_update_wrong_version(self): req = self._fake_update_request( fake.BACKUP_ID, version=mv.get_prior_version(mv.BACKUP_UPDATE)) body = {"backup": {"name": "Updated Test Name", }} self.assertRaises(exception.VersionNotFoundForAPIMethod, self.controller.update, req, fake.BACKUP_ID, body)
def test_update_consistencygroups_no_empty_parameters(self): consistencygroup = self._create_consistencygroup( ctxt=self.ctxt, status=fields.ConsistencyGroupStatus.AVAILABLE) req = fakes.HTTPRequest.blank('/v3/%s/consistencygroups/%s/update' % (fake.PROJECT_ID, consistencygroup.id)) req.environ['cinder.context'].is_admin = True non_supported_version = mv.get_prior_version( mv.CG_UPDATE_BLANK_PROPERTIES) req.headers = mv.get_mv_header(non_supported_version) req.headers['Content-Type'] = 'application/json' req.api_version_request = mv.get_api_version(non_supported_version) body = {"consistencygroup": {"name": "my_fake_cg", "description": "fake consistency group", "add_volumes": "volume-uuid-1", "remove_volumes": "volume-uuid-2, volume uuid-3", }} allow_empty = self.controller._check_update_parameters_v3( req, body['consistencygroup']['name'], body['consistencygroup']['description'], body['consistencygroup']['add_volumes'], body['consistencygroup']['remove_volumes']) self.assertEqual(False, allow_empty) consistencygroup.destroy()
def test_update_consistencygroup_all_empty_parameters_not_version_ok(self): consistencygroup = self._create_consistencygroup( ctxt=self.ctxt, status=fields.ConsistencyGroupStatus.AVAILABLE) req = fakes.HTTPRequest.blank('/v3/%s/consistencygroups/%s/update' % (fake.PROJECT_ID, consistencygroup.id)) req.environ['cinder.context'].is_admin = True non_supported_version = mv.get_prior_version( mv.CG_UPDATE_BLANK_PROPERTIES) req.headers = mv.get_mv_header(non_supported_version) req.api_version_request = mv.get_api_version(non_supported_version) req.headers['Content-Type'] = 'application/json' body = { "consistencygroup": { "name": None, "description": None, "add_volumes": None, "remove_volumes": None, } } self.assertRaisesRegex( webob.exc.HTTPBadRequest, "Name, description, " "add_volumes, and remove_volumes can not be " "all empty in the request body.", self.controller.update, req, consistencygroup.id, body) consistencygroup.destroy()
def test_volume_types_index_with_extra_specs(self): req = fakes.HTTPRequest.blank( '/v3/%s/types?extra_specs={"key1":"value1"}' % fake.PROJECT_ID, use_admin_context=False) req.api_version_request = mv.get_api_version(mv.get_prior_version( mv.SUPPORT_VOLUME_TYPE_FILTER)) res_dict = self.controller.index(req) self.assertEqual(3, len(res_dict['volume_types'])) # Test filter volume type with extra specs req = fakes.HTTPRequest.blank( '/v3/%s/types?extra_specs={"key1":"value1"}' % fake.PROJECT_ID, use_admin_context=True) req.api_version_request = mv.get_api_version( mv.SUPPORT_VOLUME_TYPE_FILTER) res_dict = self.controller.index(req) self.assertEqual(1, len(res_dict['volume_types'])) self.assertDictEqual({'key1': 'value1', 'RESKEY:availability_zones': 'az1,az2'}, res_dict['volume_types'][0]['extra_specs']) # Test filter volume type with 'availability_zones' req = fakes.HTTPRequest.blank( '/v3/%s/types?extra_specs={"RESKEY:availability_zones":"az1"}' % fake.PROJECT_ID, use_admin_context=True) req.api_version_request = mv.get_api_version( mv.SUPPORT_VOLUME_TYPE_FILTER) res_dict = self.controller.index(req) self.assertEqual(2, len(res_dict['volume_types'])) self.assertEqual( ['volume_type1', 'volume_type2'], sorted([az['name'] for az in res_dict['volume_types']]))
def test_update_consistencygroups_no_empty_parameters(self): consistencygroup = self._create_consistencygroup( ctxt=self.ctxt, status=fields.ConsistencyGroupStatus.AVAILABLE) req = fakes.HTTPRequest.blank('/v3/%s/consistencygroups/%s/update' % (fake.PROJECT_ID, consistencygroup.id)) req.environ['cinder.context'].is_admin = True non_supported_version = mv.get_prior_version( mv.CG_UPDATE_BLANK_PROPERTIES) req.headers = mv.get_mv_header(non_supported_version) req.headers['Content-Type'] = 'application/json' req.api_version_request = mv.get_api_version(non_supported_version) body = { "consistencygroup": { "name": "my_fake_cg", "description": "fake consistency group", "add_volumes": "volume-uuid-1", "remove_volumes": "volume-uuid-2, volume uuid-3", } } allow_empty = self.controller._check_update_parameters_v3( req, body['consistencygroup']['name'], body['consistencygroup']['description'], body['consistencygroup']['add_volumes'], body['consistencygroup']['remove_volumes']) self.assertEqual(False, allow_empty) consistencygroup.destroy()
def test_snapshot_create_allow_in_use_negative(self, mock_create): req = create_snapshot_query_with_metadata( '{"key2": "val2"}', mv.get_prior_version(mv.SNAPSHOT_IN_USE)) body = {'snapshot': {'volume_id': fake.VOLUME_ID}} self.controller.create(req, body=body) self.assertNotIn('allow_in_use', mock_create.call_args_list[0][1])
def _expected_vol_from_controller( self, size=v2_fakes.DEFAULT_VOL_SIZE, availability_zone=DEFAULT_AZ, description=v2_fakes.DEFAULT_VOL_DESCRIPTION, name=v2_fakes.DEFAULT_VOL_NAME, consistencygroup_id=None, source_volid=None, snapshot_id=None, metadata=None, attachments=None, volume_type=v2_fakes.DEFAULT_VOL_TYPE, status=v2_fakes.DEFAULT_VOL_STATUS, with_migration_status=False, group_id=None, req_version=None): metadata = metadata or {} attachments = attachments or [] volume = {'volume': {'attachments': attachments, 'availability_zone': availability_zone, 'bootable': 'false', 'consistencygroup_id': consistencygroup_id, 'group_id': group_id, 'created_at': datetime.datetime( 1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC), 'updated_at': datetime.datetime( 1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC), 'description': description, 'id': v2_fakes.DEFAULT_VOL_ID, 'links': [{'href': 'http://localhost/v3/%s/volumes/%s' % ( fake.PROJECT_ID, fake.VOLUME_ID), 'rel': 'self'}, {'href': 'http://localhost/%s/volumes/%s' % ( fake.PROJECT_ID, fake.VOLUME_ID), 'rel': 'bookmark'}], 'metadata': metadata, 'name': name, 'replication_status': 'disabled', 'multiattach': False, 'size': size, 'snapshot_id': snapshot_id, 'source_volid': source_volid, 'status': status, 'user_id': fake.USER_ID, 'volume_type': volume_type, 'encrypted': False}} if with_migration_status: volume['volume']['migration_status'] = None # Remove group_id if max version is less than GROUP_VOLUME. if req_version and req_version.matches( None, mv.get_prior_version(mv.GROUP_VOLUME)): volume['volume'].pop('group_id') return volume
def test_group_types_show_pre_microversion(self): self.mock_object(group_types, 'get_group_type', return_group_types_get_group_type) type_id = uuid.uuid4() req = fakes.HTTPRequest.blank( '/v3/%s/group_types/%s' % (fake.PROJECT_ID, type_id), version=mv.get_prior_version(mv.GROUP_TYPE)) self.assertRaises(exception.VersionNotFoundForAPIMethod, self.controller.show, req, type_id)
def test_backup_show_with_metadata(self, version): backup = test_utils.create_backup(self.ctxt, display_name='test_backup_metadata', status=fields.BackupStatus.AVAILABLE) # show backup metadata url = '/v3/%s/backups/%s' % (fake.PROJECT_ID, backup.id) req = fakes.HTTPRequest.blank(url, version=version) backup_get = self.controller.show(req, backup.id)['backup'] if version == mv.get_prior_version(mv.BACKUP_METADATA): self.assertNotIn('metadata', backup_get) else: self.assertIn('metadata', backup_get)
def test_message_list_with_general_filter(self, version, mock_update): url = '/v3/%s/messages' % fakes.FAKE_UUID req = fakes.HTTPRequest.blank(url, version=version, use_admin_context=False) self.controller.index(req) if version != mv.get_prior_version(mv.RESOURCE_FILTER): support_like = True if version == mv.LIKE_FILTER else False mock_update.assert_called_once_with(req.environ['cinder.context'], mock.ANY, 'message', support_like)
def test_attachment_list_with_general_filter(self, version, mock_update): url = '/v3/%s/attachments' % fake.PROJECT_ID req = fakes.HTTPRequest.blank(url, version=version, use_admin_context=False) self.controller.index(req) if version != mv.get_prior_version(mv.RESOURCE_FILTER): support_like = True if version == mv.LIKE_FILTER else False mock_update.assert_called_once_with(req.environ['cinder.context'], mock.ANY, 'attachment', support_like)
def _get_expected(self, version=mv.get_prior_version(mv.REPLICATION_CLUSTER)): if (versionutils.convert_version_to_tuple(version) >= versionutils.convert_version_to_tuple(mv.REPLICATION_CLUSTER)): return EXPECTED expect = [] for cluster in EXPECTED: cluster = cluster.copy() for key in ('replication_status', 'frozen', 'active_backend_id'): cluster.pop(key) expect.append(cluster) return expect
def test_snapshot_list_with_sort_name(self, mock_refresh): self._create_snapshot(name='test1') self._create_snapshot(name='test2') req = fakes.HTTPRequest.blank( '/v3/snapshots?sort_key=name', version=mv.get_prior_version(mv.SNAPSHOT_SORT)) self.assertRaises(exception.InvalidInput, self.controller.detail, req) req = fakes.HTTPRequest.blank('/v3/snapshots?sort_key=name', version=mv.SNAPSHOT_SORT) res_dict = self.controller.detail(req) self.assertEqual(2, len(res_dict['snapshots'])) self.assertEqual('test2', res_dict['snapshots'][0]['name']) self.assertEqual('test1', res_dict['snapshots'][1]['name'])
def test_get_all_messages_with_limit_wrong_version(self): self.create_message_for_tests() PRE_MESSAGES_PAGINATION = mv.get_prior_version(mv.MESSAGES_PAGINATION) url = '/v3/messages?limit=1' req = fakes.HTTPRequest.blank(url) req.method = 'GET' req.content_type = 'application/json' req.headers = mv.get_mv_header(PRE_MESSAGES_PAGINATION) req.api_version_request = mv.get_api_version(PRE_MESSAGES_PAGINATION) req.environ['cinder.context'].is_admin = True res = self.controller.index(req) self.assertEqual(4, len(res['messages']))
def _process_snapshot_filtering(self, context=None, filters=None, req_version=None): """Formats allowed filters""" # if the max version is less than SNAPSHOT_LIST_METADATA_FILTER # metadata based filtering is not supported if req_version.matches( None, mv.get_prior_version(mv.SNAPSHOT_LIST_METADATA_FILTER)): filters.pop('metadata', None) # Filter out invalid options allowed_search_options = self._get_snapshot_filter_options() api_utils.remove_invalid_filter_options(context, filters, allowed_search_options)
def test_snapshot_list_with_metadata_unsupported_microversion(self): # Create snapshot with metadata key1: value1 metadata = {"key1": "val1"} self._create_snapshot(metadata=metadata) # Create request with metadata filter key2: value2 req = create_snapshot_query_with_metadata( '{"key2":"val2"}', mv.get_prior_version(mv.SNAPSHOT_LIST_METADATA_FILTER)) # query controller with above request res_dict = self.controller.detail(req) # verify some snapshot is returned self.assertNotEqual(0, len(res_dict['snapshots']))
def _process_snapshot_filtering(self, context=None, filters=None, req_version=None): """Formats allowed filters""" # if the max version is less than SNAPSHOT_LIST_METADATA_FILTER # metadata based filtering is not supported if req_version.matches( None, mv.get_prior_version(mv.SNAPSHOT_LIST_METADATA_FILTER)): filters.pop('metadata', None) # Filter out invalid options allowed_search_options = self._get_snapshot_filter_options() utils.remove_invalid_filter_options(context, filters, allowed_search_options)
def _test_list(self, get_all_mock, detailed, filters=None, expected=None, version=mv.get_prior_version(mv.REPLICATION_CLUSTER)): filters = filters or {} req = FakeRequest(version=version, **filters) method = getattr(self.controller, 'detail' if detailed else 'index') clusters = method(req) filters = filters.copy() filters.setdefault('is_up', None) filters.setdefault('read_deleted', 'no') self.assertEqual(expected, clusters) get_all_mock.assert_called_once_with( req.environ['cinder.context'], get_services=False, services_summary=detailed, **filters)
def test_snapshot_list_with_metadata_unsupported_microversion( self, mock_refresh): # Create snapshot with metadata key1: value1 metadata = {"key1": "val1"} self._create_snapshot(metadata=metadata) # Create request with metadata filter key2: value2 req = create_snapshot_query_with_metadata( '{"key2":"val2"}', mv.get_prior_version(mv.SNAPSHOT_LIST_METADATA_FILTER)) # query controller with above request res_dict = self.controller.detail(req) # verify some snapshot is returned self.assertNotEqual(0, len(res_dict['snapshots']))
def test_volume_types_index_with_extra_specs(self): def _get_volume_types(extra_specs, use_admin_context=True, microversion=mv.SUPPORT_VOLUME_TYPE_FILTER): req = fakes.HTTPRequest.blank('/v3/%s/types?extra_specs=%s' % (fake.PROJECT_ID, extra_specs), use_admin_context=use_admin_context) req.api_version_request = mv.get_api_version(microversion) res_dict = self.controller.index(req) return res_dict['volume_types'] # since __DEFAULT__ type always exists, total number of volume types # is total_types_created + 1. In this case it's 4 volume_types = _get_volume_types('{"key1":"value1"}', use_admin_context=False, microversion=mv.get_prior_version( mv.SUPPORT_VOLUME_TYPE_FILTER)) self.assertEqual(4, len(volume_types)) # Test filter volume type with extra specs volume_types = _get_volume_types('{"key1":"value1"}') self.assertEqual(1, len(volume_types)) self.assertDictEqual( { 'key1': 'value1', 'RESKEY:availability_zones': 'az1,az2' }, volume_types[0]['extra_specs']) # Test filter volume type with 'availability_zones' volume_types = _get_volume_types('{"RESKEY:availability_zones":"az1"}') self.assertEqual(2, len(volume_types)) self.assertEqual(['volume_type1', 'volume_type2'], sorted([az['name'] for az in volume_types])) # Test ability for non-admin to filter with user visible extra specs volume_types = _get_volume_types('{"RESKEY:availability_zones":"az1"}', use_admin_context=False) self.assertEqual(2, len(volume_types)) self.assertEqual(['volume_type1', 'volume_type2'], sorted([az['name'] for az in volume_types])) # Test inability for non-admin to filter with sensitive extra specs volume_types = _get_volume_types('{"key1":"value1"}', use_admin_context=False) self.assertEqual(0, len(volume_types))
def test_backup_list_with_name(self, version): backup1 = test_utils.create_backup( self.ctxt, display_name='b_test_name', status=fields.BackupStatus.AVAILABLE) backup2 = test_utils.create_backup( self.ctxt, display_name='a_test_name', status=fields.BackupStatus.AVAILABLE) url = '/v3/%s/backups?sort_key=name' % fake.PROJECT_ID req = fakes.HTTPRequest.blank(url, version=version) if version == mv.get_prior_version(mv.BACKUP_SORT_NAME): self.assertRaises(exception.InvalidInput, self.controller.index, req) else: expect = backup_view.ViewBuilder().summary_list(req, [backup1, backup2]) result = self.controller.index(req) self.assertEqual(expect, result)
def test_update_consistencygroup_no_body(self): consistencygroup = self._create_consistencygroup( ctxt=self.ctxt, status=fields.ConsistencyGroupStatus.AVAILABLE) req = fakes.HTTPRequest.blank('/v3/%s/consistencygroups/%s/update' % (fake.PROJECT_ID, consistencygroup.id)) req.environ['cinder.context'].is_admin = True non_supported_version = mv.get_prior_version( mv.CG_UPDATE_BLANK_PROPERTIES) req.headers = mv.get_mv_header(non_supported_version) req.api_version_request = mv.get_api_version(non_supported_version) req.headers['Content-Type'] = 'application/json' body = None self.assertRaisesRegex(webob.exc.HTTPBadRequest, "Missing request body", self.controller.update, req, consistencygroup.id, body) consistencygroup.destroy()
def _test_list(self, get_all_mock, detailed, filters=None, expected=None, version=mv.get_prior_version(mv.REPLICATION_CLUSTER)): filters = filters or {} req = FakeRequest(version=version, **filters) method = getattr(self.controller, 'detail' if detailed else 'index') clusters = method(req) filters = filters.copy() filters.setdefault('is_up', None) filters.setdefault('read_deleted', 'no') self.assertEqual(expected, clusters) get_all_mock.assert_called_once_with(req.environ['cinder.context'], get_services=False, services_summary=detailed, **filters)
def test_update_consistencygroup_no_body(self): consistencygroup = self._create_consistencygroup( ctxt=self.ctxt, status=fields.ConsistencyGroupStatus.AVAILABLE) req = fakes.HTTPRequest.blank('/v3/%s/consistencygroups/%s/update' % (fake.PROJECT_ID, consistencygroup.id)) req.environ['cinder.context'].is_admin = True non_supported_version = mv.get_prior_version( mv.CG_UPDATE_BLANK_PROPERTIES) req.headers = mv.get_mv_header(non_supported_version) req.api_version_request = mv.get_api_version(non_supported_version) req.headers['Content-Type'] = 'application/json' body = None self.assertRaisesRegexp(webob.exc.HTTPBadRequest, "Missing request body", self.controller.update, req, consistencygroup.id, body) consistencygroup.destroy()
def _expected_volume_api_create_kwargs(self, snapshot=None, availability_zone=DEFAULT_AZ, source_volume=None, test_group=None, req_version=None): volume = { 'metadata': None, 'snapshot': snapshot, 'source_volume': source_volume, 'consistencygroup': None, 'availability_zone': availability_zone, 'scheduler_hints': None, 'multiattach': False, 'group': test_group, } # Remove group_id if max version is less than GROUP_VOLUME. if req_version and req_version.matches( None, mv.get_prior_version(mv.GROUP_VOLUME)): volume.pop('group') return volume
def index(self, req): """Return all global and rate limit information.""" context = req.environ['cinder.context'] params = req.params.copy() req_version = req.api_version_request # TODO(wangxiyuan): Support "tenant_id" here to keep the backwards # compatibility. Remove it once we drop all support for "tenant". if (req_version.matches(None, mv.get_prior_version(mv.LIMITS_ADMIN_FILTER)) or not context.is_admin): params.pop('project_id', None) params.pop('tenant_id', None) project_id = params.get('project_id', params.get('tenant_id', context.project_id)) quotas = QUOTAS.get_project_quotas(context, project_id, usages=False) abs_limits = {k: v['limit'] for k, v in quotas.items()} rate_limits = req.environ.get("cinder.limits", []) builder = self._get_view_builder(req) return builder.build(rate_limits, abs_limits)
def test_update_consistencygroup_all_empty_parameters_not_version_ok(self): consistencygroup = self._create_consistencygroup( ctxt=self.ctxt, status=fields.ConsistencyGroupStatus.AVAILABLE) req = fakes.HTTPRequest.blank('/v3/%s/consistencygroups/%s/update' % (fake.PROJECT_ID, consistencygroup.id)) req.environ['cinder.context'].is_admin = True non_supported_version = mv.get_prior_version( mv.CG_UPDATE_BLANK_PROPERTIES) req.headers = mv.get_mv_header(non_supported_version) req.api_version_request = mv.get_api_version(non_supported_version) req.headers['Content-Type'] = 'application/json' body = {"consistencygroup": {"name": None, "description": None, "add_volumes": None, "remove_volumes": None, }} self.assertRaisesRegexp(webob.exc.HTTPBadRequest, "Name, description, " "add_volumes, and remove_volumes can not be " "all empty in the request body.", self.controller.update, req, consistencygroup.id, body) consistencygroup.destroy()
class GroupSnapshotsAPITestCase(test.TestCase): """Test Case for group_snapshots API.""" def setUp(self): super(GroupSnapshotsAPITestCase, self).setUp() self.controller = v3_group_snapshots.GroupSnapshotsController() self.volume_api = cinder.volume.API() self.context = context.get_admin_context() self.context.project_id = fake.PROJECT_ID self.context.user_id = fake.USER_ID self.user_ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, auth_token=True) self.group = utils.create_group(self.context, group_type_id=fake.GROUP_TYPE_ID, volume_type_ids=[fake.VOLUME_TYPE_ID]) self.volume = utils.create_volume(self.context, group_id=self.group.id, volume_type_id=fake.VOLUME_TYPE_ID) self.g_snapshots_array = [ utils.create_group_snapshot(self.context, group_id=self.group.id, group_type_id=self.group.group_type_id) for _ in range(3) ] self.addCleanup(self._cleanup) def _cleanup(self): for snapshot in self.g_snapshots_array: snapshot.destroy() self.volume.destroy() self.group.destroy() def test_show_group_snapshot(self): group_snapshot = utils.create_group_snapshot(self.context, group_id=self.group.id) req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots/%s' % (fake.PROJECT_ID, group_snapshot.id), version=mv.GROUP_SNAPSHOTS) res_dict = self.controller.show(req, group_snapshot.id) self.assertEqual(1, len(res_dict)) self.assertEqual('this is a test group snapshot', res_dict['group_snapshot']['description']) self.assertEqual('test_group_snapshot', res_dict['group_snapshot']['name']) self.assertEqual(fields.GroupSnapshotStatus.CREATING, res_dict['group_snapshot']['status']) group_snapshot.destroy() @ddt.data(True, False) def test_list_group_snapshots_with_limit(self, is_detail): url = '/v3/%s/group_snapshots?limit=1' % fake.PROJECT_ID if is_detail: url = '/v3/%s/group_snapshots/detail?limit=1' % fake.PROJECT_ID req = fakes.HTTPRequest.blank(url, version=mv.GROUP_SNAPSHOT_PAGINATION) if is_detail: res_dict = self.controller.detail(req) else: res_dict = self.controller.index(req) self.assertEqual(2, len(res_dict)) self.assertEqual(1, len(res_dict['group_snapshots'])) self.assertEqual(self.g_snapshots_array[2].id, res_dict['group_snapshots'][0]['id']) next_link = ('http://localhost/v3/%s/group_snapshots?limit=' '1&marker=%s' % (fake.PROJECT_ID, res_dict['group_snapshots'][0]['id'])) self.assertEqual(next_link, res_dict['group_snapshot_links'][0]['href']) if is_detail: self.assertIn('description', res_dict['group_snapshots'][0].keys()) else: self.assertNotIn('description', res_dict['group_snapshots'][0].keys()) @ddt.data(True, False) def test_list_group_snapshot_with_offset(self, is_detail): url = '/v3/%s/group_snapshots?offset=1' % fake.PROJECT_ID if is_detail: url = '/v3/%s/group_snapshots/detail?offset=1' % fake.PROJECT_ID req = fakes.HTTPRequest.blank(url, version=mv.GROUP_SNAPSHOT_PAGINATION) if is_detail: res_dict = self.controller.detail(req) else: res_dict = self.controller.index(req) self.assertEqual(1, len(res_dict)) self.assertEqual(2, len(res_dict['group_snapshots'])) self.assertEqual(self.g_snapshots_array[1].id, res_dict['group_snapshots'][0]['id']) self.assertEqual(self.g_snapshots_array[0].id, res_dict['group_snapshots'][1]['id']) if is_detail: self.assertIn('description', res_dict['group_snapshots'][0].keys()) else: self.assertNotIn('description', res_dict['group_snapshots'][0].keys()) @ddt.data(True, False) def test_list_group_snapshot_with_offset_out_of_range(self, is_detail): url = ('/v3/%s/group_snapshots?offset=234523423455454' % fake.PROJECT_ID) if is_detail: url = ('/v3/%s/group_snapshots/detail?offset=234523423455454' % fake.PROJECT_ID) req = fakes.HTTPRequest.blank(url, version=mv.GROUP_SNAPSHOT_PAGINATION) if is_detail: self.assertRaises(webob.exc.HTTPBadRequest, self.controller.detail, req) else: self.assertRaises(webob.exc.HTTPBadRequest, self.controller.index, req) @ddt.data(False, True) def test_list_group_snapshot_with_limit_and_offset(self, is_detail): group_snapshot = utils.create_group_snapshot( self.context, group_id=self.group.id, group_type_id=self.group.group_type_id) url = '/v3/%s/group_snapshots?limit=2&offset=1' % fake.PROJECT_ID if is_detail: url = ('/v3/%s/group_snapshots/detail?limit=2&offset=1' % fake.PROJECT_ID) req = fakes.HTTPRequest.blank(url, version=mv.GROUP_SNAPSHOT_PAGINATION) if is_detail: res_dict = self.controller.detail(req) else: res_dict = self.controller.index(req) self.assertEqual(2, len(res_dict)) self.assertEqual(2, len(res_dict['group_snapshots'])) self.assertEqual(self.g_snapshots_array[2].id, res_dict['group_snapshots'][0]['id']) self.assertEqual(self.g_snapshots_array[1].id, res_dict['group_snapshots'][1]['id']) self.assertIsNotNone(res_dict['group_snapshot_links'][0]['href']) if is_detail: self.assertIn('description', res_dict['group_snapshots'][0].keys()) else: self.assertNotIn('description', res_dict['group_snapshots'][0].keys()) group_snapshot.destroy() @ddt.data(mv.get_prior_version(mv.RESOURCE_FILTER), mv.RESOURCE_FILTER, mv.LIKE_FILTER) @mock.patch('cinder.api.common.reject_invalid_filters') def test_group_snapshot_list_with_general_filter(self, version, mock_update): url = '/v3/%s/group_snapshots' % fake.PROJECT_ID req = fakes.HTTPRequest.blank(url, version=version, use_admin_context=False) self.controller.index(req) if version != mv.get_prior_version(mv.RESOURCE_FILTER): support_like = True if version == mv.LIKE_FILTER else False mock_update.assert_called_once_with(req.environ['cinder.context'], mock.ANY, 'group_snapshot', support_like) @ddt.data(False, True) def test_list_group_snapshot_with_filter(self, is_detail): url = ('/v3/%s/group_snapshots?' 'all_tenants=True&id=%s') % (fake.PROJECT_ID, self.g_snapshots_array[0].id) if is_detail: url = ('/v3/%s/group_snapshots/detail?' 'all_tenants=True&id=%s') % (fake.PROJECT_ID, self.g_snapshots_array[0].id) req = fakes.HTTPRequest.blank(url, version=mv.GROUP_SNAPSHOT_PAGINATION, use_admin_context=True) if is_detail: res_dict = self.controller.detail(req) else: res_dict = self.controller.index(req) self.assertEqual(1, len(res_dict)) self.assertEqual(1, len(res_dict['group_snapshots'])) self.assertEqual(self.g_snapshots_array[0].id, res_dict['group_snapshots'][0]['id']) if is_detail: self.assertIn('description', res_dict['group_snapshots'][0].keys()) else: self.assertNotIn('description', res_dict['group_snapshots'][0].keys()) @ddt.data( { 'is_detail': True, 'version': mv.GROUP_SNAPSHOTS }, { 'is_detail': False, 'version': mv.GROUP_SNAPSHOTS }, { 'is_detail': True, 'version': mv.POOL_FILTER }, { 'is_detail': False, 'version': mv.POOL_FILTER }, ) @ddt.unpack def test_list_group_snapshot_with_filter_previous_version( self, is_detail, version): url = ('/v3/%s/group_snapshots?' 'all_tenants=True&id=%s') % (fake.PROJECT_ID, self.g_snapshots_array[0].id) if is_detail: url = ('/v3/%s/group_snapshots/detail?' 'all_tenants=True&id=%s') % (fake.PROJECT_ID, self.g_snapshots_array[0].id) req = fakes.HTTPRequest.blank(url, version=version, use_admin_context=True) if is_detail: res_dict = self.controller.detail(req) else: res_dict = self.controller.index(req) self.assertEqual(1, len(res_dict)) self.assertEqual(3, len(res_dict['group_snapshots'])) @ddt.data(False, True) def test_list_group_snapshot_with_sort(self, is_detail): url = '/v3/%s/group_snapshots?sort=id:asc' % fake.PROJECT_ID if is_detail: url = ('/v3/%s/group_snapshots/detail?sort=id:asc' % fake.PROJECT_ID) req = fakes.HTTPRequest.blank(url, version=mv.GROUP_SNAPSHOT_PAGINATION) expect_result = [snapshot.id for snapshot in self.g_snapshots_array] expect_result.sort() if is_detail: res_dict = self.controller.detail(req) else: res_dict = self.controller.index(req) self.assertEqual(1, len(res_dict)) self.assertEqual(3, len(res_dict['group_snapshots'])) self.assertEqual(expect_result[0], res_dict['group_snapshots'][0]['id']) self.assertEqual(expect_result[1], res_dict['group_snapshots'][1]['id']) self.assertEqual(expect_result[2], res_dict['group_snapshots'][2]['id']) if is_detail: self.assertIn('description', res_dict['group_snapshots'][0].keys()) else: self.assertNotIn('description', res_dict['group_snapshots'][0].keys()) def test_show_group_snapshot_with_group_snapshot_not_found(self): req = fakes.HTTPRequest.blank( '/v3/%s/group_snapshots/%s' % (fake.PROJECT_ID, fake.WILL_NOT_BE_FOUND_ID), version=mv.GROUP_SNAPSHOTS) self.assertRaises(exception.GroupSnapshotNotFound, self.controller.show, req, fake.WILL_NOT_BE_FOUND_ID) @ddt.data(True, False) def test_list_group_snapshots_json(self, is_detail): if is_detail: request_url = '/v3/%s/group_snapshots/detail' else: request_url = '/v3/%s/group_snapshots' req = fakes.HTTPRequest.blank(request_url % fake.PROJECT_ID, version=mv.GROUP_SNAPSHOTS) if is_detail: res_dict = self.controller.detail(req) else: res_dict = self.controller.index(req) self.assertEqual(1, len(res_dict)) self.assertEqual(3, len(res_dict['group_snapshots'])) for index, snapshot in enumerate(self.g_snapshots_array): self.assertEqual(snapshot.id, res_dict['group_snapshots'][2 - index]['id']) self.assertIsNotNone(res_dict['group_snapshots'][2 - index]['name']) if is_detail: self.assertIn('description', res_dict['group_snapshots'][2 - index].keys()) else: self.assertNotIn('description', res_dict['group_snapshots'][2 - index].keys()) @mock.patch('cinder.db.volume_type_get') @mock.patch('cinder.quota.VolumeTypeQuotaEngine.reserve') def test_create_group_snapshot_json(self, mock_quota, mock_vol_type): body = { "group_snapshot": { "name": "group_snapshot1", "description": "Group Snapshot 1", "group_id": self.group.id } } req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots' % fake.PROJECT_ID, version=mv.GROUP_SNAPSHOTS) res_dict = self.controller.create(req, body=body) self.assertEqual(1, len(res_dict)) self.assertIn('id', res_dict['group_snapshot']) group_snapshot = objects.GroupSnapshot.get_by_id( context.get_admin_context(), res_dict['group_snapshot']['id']) group_snapshot.destroy() @mock.patch('cinder.db.volume_type_get') def test_create_group_snapshot_when_volume_in_error_status( self, mock_vol_type): group = utils.create_group( self.context, group_type_id=fake.GROUP_TYPE_ID, volume_type_ids=[fake.VOLUME_TYPE_ID], ) volume_id = utils.create_volume( self.context, status='error', group_id=group.id, volume_type_id=fake.VOLUME_TYPE_ID)['id'] body = { "group_snapshot": { "name": "group_snapshot1", "description": "Group Snapshot 1", "group_id": group.id } } req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots' % fake.PROJECT_ID, version=mv.GROUP_SNAPSHOTS) self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, req, body=body) group.destroy() db.volume_destroy(context.get_admin_context(), volume_id) def test_create_group_snapshot_with_no_body(self): # omit body from the request req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots' % fake.PROJECT_ID, version=mv.GROUP_SNAPSHOTS) self.assertRaises(exception.ValidationError, self.controller.create, req, body=None) def test_create_group_snapshot_with_empty_body(self): # empty body in the request req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots' % fake.PROJECT_ID, version=mv.GROUP_SNAPSHOTS) body = {"group_snapshot": {}} self.assertRaises(exception.ValidationError, self.controller.create, req, body=body) @mock.patch.object(group_api.API, 'create_group_snapshot', side_effect=exception.InvalidGroupSnapshot( reason='Invalid group snapshot')) def test_create_with_invalid_group_snapshot(self, mock_create_group_snap): body = { "group_snapshot": { "name": "group_snapshot1", "description": "Group Snapshot 1", "group_id": self.group.id } } req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots' % fake.PROJECT_ID, version=mv.GROUP_SNAPSHOTS) self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, req, body=body) @mock.patch.object(group_api.API, 'create_group_snapshot', side_effect=exception.GroupSnapshotNotFound( group_snapshot_id='invalid_id')) def test_create_with_group_snapshot_not_found(self, mock_create_grp_snap): body = { "group_snapshot": { "name": "group_snapshot1", "description": "Group Snapshot 1", "group_id": self.group.id } } req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots' % fake.PROJECT_ID, version=mv.GROUP_SNAPSHOTS) self.assertRaises(exception.GroupSnapshotNotFound, self.controller.create, req, body=body) def test_create_group_snapshot_from_empty_group(self): empty_group = utils.create_group(self.context, group_type_id=fake.GROUP_TYPE_ID, volume_type_ids=[fake.VOLUME_TYPE_ID]) body = { "group_snapshot": { "name": "group_snapshot1", "description": "Group Snapshot 1", "group_id": empty_group.id } } req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots' % fake.PROJECT_ID, version=mv.GROUP_SNAPSHOTS) self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, req, body=body) empty_group.destroy() def test_delete_group_snapshot_available(self): group_snapshot = utils.create_group_snapshot( self.context, group_id=self.group.id, status=fields.GroupSnapshotStatus.AVAILABLE) req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots/%s' % (fake.PROJECT_ID, group_snapshot.id), version=mv.GROUP_SNAPSHOTS) res_dict = self.controller.delete(req, group_snapshot.id) group_snapshot = objects.GroupSnapshot.get_by_id( self.context, group_snapshot.id) self.assertEqual(http_client.ACCEPTED, res_dict.status_int) self.assertEqual(fields.GroupSnapshotStatus.DELETING, group_snapshot.status) group_snapshot.destroy() def test_delete_group_snapshot_available_used_as_source(self): group_snapshot = utils.create_group_snapshot( self.context, group_id=self.group.id, status=fields.GroupSnapshotStatus.AVAILABLE) group2 = utils.create_group( self.context, status='creating', group_snapshot_id=group_snapshot.id, group_type_id=fake.GROUP_TYPE_ID, volume_type_ids=[fake.VOLUME_TYPE_ID], ) req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots/%s' % (fake.PROJECT_ID, group_snapshot.id), version=mv.GROUP_SNAPSHOTS) self.assertRaises(webob.exc.HTTPBadRequest, self.controller.delete, req, group_snapshot.id) group_snapshot.destroy() group2.destroy() def test_delete_group_snapshot_with_group_snapshot_NotFound(self): req = fakes.HTTPRequest.blank( '/v3/%s/group_snapshots/%s' % (fake.PROJECT_ID, fake.WILL_NOT_BE_FOUND_ID), version=mv.GROUP_SNAPSHOTS) self.assertRaises(exception.GroupSnapshotNotFound, self.controller.delete, req, fake.WILL_NOT_BE_FOUND_ID) def test_delete_group_snapshot_with_invalid_group_snapshot(self): group_snapshot = utils.create_group_snapshot(self.context, group_id=self.group.id, status='invalid') req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots/%s' % (fake.PROJECT_ID, group_snapshot.id), version=mv.GROUP_SNAPSHOTS) self.assertRaises(webob.exc.HTTPBadRequest, self.controller.delete, req, group_snapshot.id) group_snapshot.destroy() @ddt.data( (mv.GROUP_TYPE, 'fake_snapshot_001', fields.GroupSnapshotStatus.AVAILABLE, exception.VersionNotFoundForAPIMethod), (mv.get_prior_version(mv.GROUP_SNAPSHOT_RESET_STATUS), 'fake_snapshot_001', fields.GroupSnapshotStatus.AVAILABLE, exception.VersionNotFoundForAPIMethod), (mv.GROUP_SNAPSHOT_RESET_STATUS, 'fake_snapshot_001', fields.GroupSnapshotStatus.AVAILABLE, exception.GroupSnapshotNotFound) ) @ddt.unpack def test_reset_group_snapshot_status_illegal(self, version, group_snapshot_id, status, exceptions): req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots/%s/action' % (fake.PROJECT_ID, group_snapshot_id), version=version) body = {"reset_status": {"status": status}} self.assertRaises(exceptions, self.controller.reset_status, req, group_snapshot_id, body=body) def test_reset_group_snapshot_status_invalid_status(self): group_snapshot = utils.create_group_snapshot( self.context, group_id=self.group.id, status=fields.GroupSnapshotStatus.CREATING) req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots/%s/action' % (fake.PROJECT_ID, group_snapshot.id), version=mv.GROUP_SNAPSHOT_RESET_STATUS) body = {"reset_status": {"status": "invalid_test_status"}} self.assertRaises(exception.InvalidGroupSnapshotStatus, self.controller.reset_status, req, group_snapshot.id, body=body) group_snapshot.destroy() def test_reset_group_snapshot_status(self): group_snapshot = utils.create_group_snapshot( self.context, group_id=self.group.id, status=fields.GroupSnapshotStatus.CREATING) req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots/%s/action' % (fake.PROJECT_ID, group_snapshot.id), version=mv.GROUP_SNAPSHOT_RESET_STATUS) body = { "reset_status": { "status": fields.GroupSnapshotStatus.AVAILABLE } } response = self.controller.reset_status(req, group_snapshot.id, body=body) g_snapshot = objects.GroupSnapshot.get_by_id(self.context, group_snapshot.id) self.assertEqual(http_client.ACCEPTED, response.status_int) self.assertEqual(fields.GroupSnapshotStatus.AVAILABLE, g_snapshot.status) group_snapshot.destroy()
def test_update_wrong_version(self, action): req = FakeRequest(version=mv.get_prior_version(mv.CLUSTER_SUPPORT)) self.assertRaises(exception.VersionNotFoundForAPIMethod, self.controller.update, req, action, {})
def test_index_wrong_version(self, detailed): """Verify the wrong version so that user can't list clusters.""" self.assertRaises(exception.VersionNotFoundForAPIMethod, self._test_list, detailed=detailed, version=mv.get_prior_version(mv.CLUSTER_SUPPORT))
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import ddt import mock from cinder.api import microversions as mv from cinder.api.openstack import api_version_request as api_version from cinder.api.v3 import limits from cinder import test from cinder.tests.unit.api import fakes from cinder.tests.unit import fake_constants as fake LIMITS_FILTER = mv.LIMITS_ADMIN_FILTER PRE_LIMITS_FILTER = mv.get_prior_version(LIMITS_FILTER) @ddt.ddt class LimitsControllerTest(test.TestCase): def setUp(self): super(LimitsControllerTest, self).setUp() self.controller = limits.LimitsController() @ddt.data((PRE_LIMITS_FILTER, True), (PRE_LIMITS_FILTER, False), (LIMITS_FILTER, True), (LIMITS_FILTER, False)) @mock.patch('cinder.quota.VolumeTypeQuotaEngine.get_project_quotas') def test_get_limit_with_project_id(self, ver_project, mock_get_quotas): max_ver, has_project = ver_project req = fakes.HTTPRequest.blank('/v3/limits', use_admin_context=True) if has_project:
def test_get_backup_under_allowed_api_version(self): ctx = context.RequestContext(fake.USER2_ID, fake.PROJECT_ID, True) bak = self._send_backup_request( ctx, version=mv.get_prior_version(mv.BACKUP_PROJECT)) self.assertNotIn('os-backup-project-attr:project_id', bak)
def test_get_backup_user_id_before_microversion_v356(self): ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True) bak = self._send_backup_request( ctx, version=mv.get_prior_version(mv.BACKUP_PROJECT_USER_ID)) self.assertNotIn('user_id', bak)
def test_manage_snapshot_previous_version(self): body = {'snapshot': {'volume_id': fake.VOLUME_ID, 'ref': 'fake_ref'}} res = self._get_resp_post( body, version=mv.get_prior_version(mv.MANAGE_EXISTING_LIST)) self.assertEqual(http_client.NOT_FOUND, res.status_int, res)
def test_get_manageable_volumes_detail_previous_version(self): res = self._get_resp_get( 'fakehost', True, False, version=mv.get_prior_version(mv.MANAGE_EXISTING_LIST)) self.assertEqual(http_client.NOT_FOUND, res.status_int)
def create(self, req, body): """Creates a new volume. :param req: the request :param body: the request body :returns: dict -- the new volume dictionary :raises HTTPNotFound, HTTPBadRequest: """ self.assert_valid_body(body, 'volume') LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] req_version = req.api_version_request # Remove group_id from body if max version is less than GROUP_VOLUME. if req_version.matches(None, mv.get_prior_version(mv.GROUP_VOLUME)): # NOTE(xyang): The group_id is from a group created with a # group_type. So with this group_id, we've got a group_type # for this volume. Also if group_id is passed in, that means # we already know which backend is hosting the group and the # volume will be created on the same backend as well. So it # won't go through the scheduler again if a group_id is # passed in. try: body.get('volume', {}).pop('group_id', None) except AttributeError: msg = (_("Invalid body provided for creating volume. " "Request API version: %s.") % req_version) raise exc.HTTPBadRequest(explanation=msg) volume = body['volume'] kwargs = {} self.validate_name_and_description(volume) # Check up front for legacy replication parameters to quick fail source_replica = volume.get('source_replica') if source_replica: msg = _("Creating a volume from a replica source was part of the " "replication v1 implementation which is no longer " "available.") raise exception.InvalidInput(reason=msg) # NOTE(thingee): v2 API allows name instead of display_name if 'name' in volume: volume['display_name'] = volume.pop('name') # NOTE(thingee): v2 API allows description instead of # display_description if 'description' in volume: volume['display_description'] = volume.pop('description') if 'image_id' in volume: volume['imageRef'] = volume.pop('image_id') req_volume_type = volume.get('volume_type', None) if req_volume_type: # Not found exception will be handled at the wsgi level kwargs['volume_type'] = ( objects.VolumeType.get_by_name_or_id(context, req_volume_type)) kwargs['metadata'] = volume.get('metadata', None) snapshot_id = volume.get('snapshot_id') if snapshot_id is not None: if not uuidutils.is_uuid_like(snapshot_id): msg = _("Snapshot ID must be in UUID form.") raise exc.HTTPBadRequest(explanation=msg) # Not found exception will be handled at the wsgi level kwargs['snapshot'] = self.volume_api.get_snapshot(context, snapshot_id) else: kwargs['snapshot'] = None source_volid = volume.get('source_volid') if source_volid is not None: if not uuidutils.is_uuid_like(source_volid): msg = _("Source volume ID '%s' must be a " "valid UUID.") % source_volid raise exc.HTTPBadRequest(explanation=msg) # Not found exception will be handled at the wsgi level kwargs['source_volume'] = ( self.volume_api.get_volume(context, source_volid)) else: kwargs['source_volume'] = None kwargs['group'] = None kwargs['consistencygroup'] = None consistencygroup_id = volume.get('consistencygroup_id') if consistencygroup_id is not None: if not uuidutils.is_uuid_like(consistencygroup_id): msg = _("Consistency group ID '%s' must be a " "valid UUID.") % consistencygroup_id raise exc.HTTPBadRequest(explanation=msg) # Not found exception will be handled at the wsgi level kwargs['group'] = self.group_api.get(context, consistencygroup_id) # Get group_id if volume is in a group. group_id = volume.get('group_id') if group_id is not None: # Not found exception will be handled at the wsgi level kwargs['group'] = self.group_api.get(context, group_id) if self.ext_mgr.is_loaded('os-image-create'): image_ref = volume.get('imageRef') if image_ref is not None: image_uuid = self._image_uuid_from_ref(image_ref, context) image_snapshot = self._get_image_snapshot(context, image_uuid) if (req_version.matches(mv.get_api_version( mv.SUPPORT_NOVA_IMAGE)) and image_snapshot): kwargs['snapshot'] = image_snapshot else: kwargs['image_id'] = image_uuid # Add backup if min version is greater than or equal # to VOLUME_CREATE_FROM_BACKUP. if req_version.matches(mv.VOLUME_CREATE_FROM_BACKUP, None): backup_id = volume.get('backup_id') if backup_id: if not uuidutils.is_uuid_like(backup_id): msg = _("Backup ID must be in UUID form.") raise exc.HTTPBadRequest(explanation=msg) kwargs['backup'] = self.backup_api.get(context, backup_id=backup_id) else: kwargs['backup'] = None size = volume.get('size', None) if size is None and kwargs['snapshot'] is not None: size = kwargs['snapshot']['volume_size'] elif size is None and kwargs['source_volume'] is not None: size = kwargs['source_volume']['size'] elif size is None and kwargs.get('backup') is not None: size = kwargs['backup']['size'] LOG.info("Create volume of %s GB", size) kwargs['availability_zone'] = volume.get('availability_zone', None) kwargs['scheduler_hints'] = volume.get('scheduler_hints', None) multiattach = volume.get('multiattach', False) kwargs['multiattach'] = multiattach if multiattach: msg = ("The option 'multiattach' " "is deprecated and will be removed in a future " "release. The default behavior going forward will " "be to specify multiattach enabled volume types.") versionutils.report_deprecated_feature(LOG, msg) new_volume = self.volume_api.create(context, size, volume.get('display_name'), volume.get('display_description'), **kwargs) retval = self._view_builder.detail(req, new_volume) return retval
def test_cleanup_old_api_version(self, rpc_mock): res = self._get_resp_post({}, mv.get_prior_version(mv.WORKERS_CLEANUP)) self.assertEqual(http_client.NOT_FOUND, res.status_code) rpc_mock.assert_not_called()
def test_manage_snapshot_previous_version(self): body = {'snapshot': {'volume_id': fake.VOLUME_ID, 'ref': 'fake_ref'}} res = self._get_resp_post(body, version=mv.get_prior_version( mv.MANAGE_EXISTING_LIST)) self.assertEqual(http_client.NOT_FOUND, res.status_int, res)
def test_get_backup_under_allowed_api_version(self): ctx = context.RequestContext(fake.USER2_ID, fake.PROJECT_ID, True) bak = self._send_backup_request(ctx, version=mv.get_prior_version( mv.BACKUP_PROJECT)) self.assertNotIn('os-backup-project-attr:project_id', bak)
def test_volumes_summary_in_unsupport_version(self): """Function call to test summary volumes API in unsupported version""" req = self._fake_volumes_summary_request( version=mv.get_prior_version(mv.VOLUME_SUMMARY)) self.assertRaises(exception.VersionNotFoundForAPIMethod, self.controller.summary, req)
class VolumeController(volumes_v2.VolumeController): """The Volumes API controller for the OpenStack API V3.""" _view_builder_class = volume_views_v3.ViewBuilder def __init__(self, ext_mgr): self.group_api = group_api.API() self.backup_api = backup_api.API() super(VolumeController, self).__init__(ext_mgr) def delete(self, req, id): """Delete a volume.""" context = req.environ['cinder.context'] req_version = req.api_version_request cascade = utils.get_bool_param('cascade', req.params) force = False params = "" if req_version.matches(mv.VOLUME_DELETE_FORCE): force = utils.get_bool_param('force', req.params) if cascade or force: params = "(cascade: %(c)s, force: %(f)s)" % { 'c': cascade, 'f': force } LOG.info("Delete volume with id: %(id)s %(params)s", { 'id': id, 'params': params }, context=context) volume = self.volume_api.get(context, id) if force: context.authorize(policy.FORCE_DELETE_POLICY, target_obj=volume) self.volume_api.delete(context, volume, cascade=cascade, force=force) return webob.Response(status_int=http_client.ACCEPTED) @common.process_general_filtering('volume') def _process_volume_filtering(self, context=None, filters=None, req_version=None): if req_version.matches(None, mv.MESSAGES): filters.pop('glance_metadata', None) if req_version.matches(None, mv.BACKUP_UPDATE): filters.pop('group_id', None) utils.remove_invalid_filter_options(context, filters, self._get_volume_filter_options()) def _get_volumes(self, req, is_detail): """Returns a list of volumes, transformed through view builder.""" context = req.environ['cinder.context'] req_version = req.api_version_request params = req.params.copy() marker, limit, offset = common.get_pagination_params(params) sort_keys, sort_dirs = common.get_sort_params(params) filters = params show_count = False if req_version.matches( mv.SUPPORT_COUNT_INFO) and 'with_count' in filters: show_count = utils.get_bool_param('with_count', filters) filters.pop('with_count') self._process_volume_filtering(context=context, filters=filters, req_version=req_version) # NOTE(thingee): v2 API allows name instead of display_name if 'name' in sort_keys: sort_keys[sort_keys.index('name')] = 'display_name' if 'name' in filters: filters['display_name'] = filters.pop('name') strict = req.api_version_request.matches(mv.VOLUME_LIST_BOOTABLE, None) self.volume_api.check_volume_filters(filters, strict) volumes = self.volume_api.get_all(context, marker, limit, sort_keys=sort_keys, sort_dirs=sort_dirs, filters=filters.copy(), viewable_admin_meta=True, offset=offset) total_count = None if show_count: total_count = self.volume_api.calculate_resource_count( context, 'volume', filters) for volume in volumes: utils.add_visible_admin_metadata(volume) req.cache_db_volumes(volumes.objects) if is_detail: volumes = self._view_builder.detail_list(req, volumes, total_count) else: volumes = self._view_builder.summary_list(req, volumes, total_count) return volumes @wsgi.Controller.api_version(mv.VOLUME_SUMMARY) def summary(self, req): """Return summary of volumes.""" view_builder_v3 = volume_views_v3.ViewBuilder() context = req.environ['cinder.context'] filters = req.params.copy() utils.remove_invalid_filter_options(context, filters, self._get_volume_filter_options()) num_vols, sum_size, metadata = self.volume_api.get_volume_summary( context, filters=filters) req_version = req.api_version_request if req_version.matches(mv.VOLUME_SUMMARY_METADATA): all_distinct_metadata = metadata else: all_distinct_metadata = None return view_builder_v3.quick_summary(num_vols, int(sum_size), all_distinct_metadata) @wsgi.response(http_client.ACCEPTED) @wsgi.Controller.api_version(mv.VOLUME_REVERT) @wsgi.action('revert') def revert(self, req, id, body): """revert a volume to a snapshot""" context = req.environ['cinder.context'] self.assert_valid_body(body, 'revert') snapshot_id = body['revert'].get('snapshot_id') volume = self.volume_api.get_volume(context, id) try: l_snap = volume.get_latest_snapshot() except exception.VolumeSnapshotNotFound: msg = _("Volume %s doesn't have any snapshots.") raise exc.HTTPBadRequest(explanation=msg % volume.id) # Ensure volume and snapshot match. if snapshot_id is None or snapshot_id != l_snap.id: msg = _("Specified snapshot %(s_id)s is None or not " "the latest one of volume %(v_id)s.") raise exc.HTTPBadRequest(explanation=msg % { 's_id': snapshot_id, 'v_id': volume.id }) if volume.size != l_snap.volume_size: msg = _("Can't revert volume %(v_id)s to its latest snapshot " "%(s_id)s. The volume size must be equal to the snapshot " "size.") raise exc.HTTPBadRequest(explanation=msg % { 's_id': snapshot_id, 'v_id': volume.id }) try: msg = 'Reverting volume %(v_id)s to snapshot %(s_id)s.' LOG.info(msg, {'v_id': volume.id, 's_id': l_snap.id}) self.volume_api.revert_to_snapshot(context, volume, l_snap) except (exception.InvalidVolume, exception.InvalidSnapshot) as e: raise exc.HTTPConflict(explanation=six.text_type(e)) except exception.VolumeSizeExceedsAvailableQuota as e: raise exc.HTTPForbidden(explanation=six.text_type(e)) def _get_image_snapshot(self, context, image_uuid): image_snapshot = None if image_uuid: image_service = glance.get_default_image_service() image_meta = image_service.show(context, image_uuid) if image_meta is not None: bdms = image_meta.get('properties', {}).get('block_device_mapping', []) if bdms: boot_bdm = [ bdm for bdm in bdms if (bdm.get('source_type') == 'snapshot' and bdm.get('boot_index') == 0) ] if boot_bdm: try: image_snapshot = self.volume_api.get_snapshot( context, boot_bdm[0].get('snapshot_id')) return image_snapshot except exception.NotFound: explanation = _( 'Nova specific image is found, but boot ' 'volume snapshot id:%s not found.' ) % boot_bdm[0].get('snapshot_id') raise exc.HTTPNotFound(explanation=explanation) return image_snapshot @wsgi.response(http_client.ACCEPTED) @validation.schema(volumes.create, mv.BASE_VERSION, mv.get_prior_version(mv.GROUP_VOLUME)) @validation.schema(volumes.create_volume_v313, mv.GROUP_VOLUME, mv.get_prior_version(mv.VOLUME_CREATE_FROM_BACKUP)) @validation.schema(volumes.create_volume_v347, mv.VOLUME_CREATE_FROM_BACKUP, mv.get_prior_version(mv.SUPPORT_VOLUME_SCHEMA_CHANGES)) @validation.schema(volumes.create_volume_v353, mv.SUPPORT_VOLUME_SCHEMA_CHANGES) def create(self, req, body): """Creates a new volume. :param req: the request :param body: the request body :returns: dict -- the new volume dictionary :raises HTTPNotFound, HTTPBadRequest: """ LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] req_version = req.api_version_request # NOTE (pooja_jadhav) To fix bug 1774155, scheduler hints is not # loaded as a standard extension. If user passes # OS-SCH-HNT:scheduler_hints in the request body, then it will be # validated in the create method and this method will add # scheduler_hints in body['volume']. body = scheduler_hints.create(req, body) volume = body['volume'] kwargs = {} self.validate_name_and_description(volume, check_length=False) # NOTE(thingee): v2 API allows name instead of display_name if 'name' in volume: volume['display_name'] = volume.pop('name') # NOTE(thingee): v2 API allows description instead of # display_description if 'description' in volume: volume['display_description'] = volume.pop('description') if 'image_id' in volume: volume['imageRef'] = volume.pop('image_id') req_volume_type = volume.get('volume_type', None) if req_volume_type: # Not found exception will be handled at the wsgi level kwargs['volume_type'] = (objects.VolumeType.get_by_name_or_id( context, req_volume_type)) kwargs['metadata'] = volume.get('metadata', None) snapshot_id = volume.get('snapshot_id') if snapshot_id is not None: # Not found exception will be handled at the wsgi level kwargs['snapshot'] = self.volume_api.get_snapshot( context, snapshot_id) else: kwargs['snapshot'] = None source_volid = volume.get('source_volid') if source_volid is not None: # Not found exception will be handled at the wsgi level kwargs['source_volume'] = (self.volume_api.get_volume( context, source_volid)) else: kwargs['source_volume'] = None kwargs['group'] = None kwargs['consistencygroup'] = None consistencygroup_id = volume.get('consistencygroup_id') if consistencygroup_id is not None: # Not found exception will be handled at the wsgi level kwargs['group'] = self.group_api.get(context, consistencygroup_id) # Get group_id if volume is in a group. group_id = volume.get('group_id') if group_id is not None: # Not found exception will be handled at the wsgi level kwargs['group'] = self.group_api.get(context, group_id) image_ref = volume.get('imageRef') if image_ref is not None: image_uuid = self._image_uuid_from_ref(image_ref, context) image_snapshot = self._get_image_snapshot(context, image_uuid) if (req_version.matches(mv.get_api_version(mv.SUPPORT_NOVA_IMAGE)) and image_snapshot): kwargs['snapshot'] = image_snapshot else: kwargs['image_id'] = image_uuid backup_id = volume.get('backup_id') if backup_id: kwargs['backup'] = self.backup_api.get(context, backup_id=backup_id) size = volume.get('size', None) if size is None and kwargs['snapshot'] is not None: size = kwargs['snapshot']['volume_size'] elif size is None and kwargs['source_volume'] is not None: size = kwargs['source_volume']['size'] elif size is None and kwargs.get('backup') is not None: size = kwargs['backup']['size'] LOG.info("Create volume of %s GB", size) kwargs['availability_zone'] = volume.get('availability_zone', None) kwargs['scheduler_hints'] = volume.get('scheduler_hints', None) multiattach = volume.get('multiattach', False) kwargs['multiattach'] = multiattach if multiattach: msg = ("The option 'multiattach' " "is deprecated and will be removed in a future " "release. The default behavior going forward will " "be to specify multiattach enabled volume types.") versionutils.report_deprecated_feature(LOG, msg) new_volume = self.volume_api.create(context, size, volume.get('display_name'), volume.get('display_description'), **kwargs) retval = self._view_builder.detail(req, new_volume) return retval
def test_show_wrong_version(self): req = FakeRequest(version=mv.get_prior_version(mv.CLUSTER_SUPPORT)) self.assertRaises(exception.VersionNotFoundForAPIMethod, self.controller.show, req, 'name')