def prepare_request_body(context, body, is_create, resource, attr_info, allow_bulk=False): """Verifies required attributes are in request body. Also checking that an attribute is only specified if it is allowed for the given operation (create/update). Attribute with default values are considered to be optional. body argument must be the deserialized body. """ collection = resource + "s" if not body: raise webob.exc.HTTPBadRequest(_("Resource body required")) LOG.debug("Request body: %(body)s", {'body': body}) try: if collection in body: if not allow_bulk: raise webob.exc.HTTPBadRequest( _("Bulk operation " "not supported")) if not body[collection]: raise webob.exc.HTTPBadRequest(_("Resources required")) bulk_body = [ Controller.prepare_request_body( context, item if resource in item else {resource: item}, is_create, resource, attr_info, allow_bulk) for item in body[collection] ] return {collection: bulk_body} res_dict = body.get(resource) except (AttributeError, TypeError): msg = _("Body contains invalid data") raise webob.exc.HTTPBadRequest(msg) if res_dict is None: msg = _("Unable to find '%s' in request body") % resource raise webob.exc.HTTPBadRequest(msg) attr_ops = attributes.AttributeInfo(attr_info) attr_ops.populate_project_id(context, res_dict, is_create) attributes.populate_project_info(attr_info) attr_ops.verify_attributes(res_dict) if is_create: # POST attr_ops.fill_post_defaults(res_dict, exc_cls=webob.exc.HTTPBadRequest) else: # PUT for attr, attr_vals in attr_info.items(): if attr in res_dict and not attr_vals['allow_put']: msg = _("Cannot update read-only attribute %s") % attr raise webob.exc.HTTPBadRequest(msg) attr_ops.convert_values(res_dict, exc_cls=webob.exc.HTTPBadRequest) return body
def test_verify_attributes_unrecognized(self): with testtools.ExpectedException(exc.HTTPBadRequest) as bad_req: attributes.AttributeInfo( {'attr1': 'foo'}).verify_attributes( {'attr1': 'foo', 'attr2': 'bar'}) self.assertEqual(bad_req.message, "Unrecognized attribute(s) 'attr2'")
def test_populate_project_id_not_mandatory(self): ctx = context.Context(user_id=None) # if the tenant_id is not mandatory for the resource it should be # OK if it is not in the request. res_dict = {'name': 'test_port'} attr_inst = attributes.AttributeInfo({}) ctx.tenant_id = None attr_inst.populate_project_id(ctx, res_dict, True)
def test_convert_value(self): attr_info = { 'key': {}, } attr_inst = attributes.AttributeInfo(attr_info) self._test_convert_value(attr_inst, {'key': constants.ATTR_NOT_SPECIFIED}, {'key': constants.ATTR_NOT_SPECIFIED}) self._test_convert_value(attr_inst, {'key': 'X'}, {'key': 'X'}) self._test_convert_value(attr_inst, {'other_key': 'X'}, {'other_key': 'X'}) attr_info = { 'key': { 'convert_to': converters.convert_to_int, }, } attr_inst = attributes.AttributeInfo(attr_info) self._test_convert_value(attr_inst, {'key': constants.ATTR_NOT_SPECIFIED}, {'key': constants.ATTR_NOT_SPECIFIED}) self._test_convert_value(attr_inst, {'key': 1}, {'key': '1'}) self._test_convert_value(attr_inst, {'key': 1}, {'key': 1}) self.assertRaises(exceptions.InvalidInput, self._test_convert_value, attr_inst, {'key': 1}, {'key': 'a'}) attr_info = { 'key': { 'validate': { 'type:uuid': None }, }, } attr_inst = attributes.AttributeInfo(attr_info) self._test_convert_value(attr_inst, {'key': constants.ATTR_NOT_SPECIFIED}, {'key': constants.ATTR_NOT_SPECIFIED}) uuid_str = '01234567-1234-1234-1234-1234567890ab' self._test_convert_value(attr_inst, {'key': uuid_str}, {'key': uuid_str}) self.assertRaises(exceptions.InvalidInput, self._test_convert_value, attr_inst, {'key': 1}, {'key': 1}) self.assertRaises(self._EXC_CLS, attr_inst.convert_values, {'key': 1}, self._EXC_CLS)
def test_fill_none_overridden_by_default(self): attr_info = { 'key': { 'allow_post': True, 'default': 42, 'default_overrides_none': True, }, } attr_inst = attributes.AttributeInfo(attr_info) self._test_fill_default_value(attr_inst, {'key': 42}, {'key': None})
def test_override_no_allow_post(self): attr_info = { 'key': { 'allow_post': False, 'default': constants.ATTR_NOT_SPECIFIED, }, } attr_inst = attributes.AttributeInfo(attr_info) self._test_fill_default_value(attr_inst, {'key': 'X'}, {'key': 'X'}, check_allow_post=False)
def test_fill_default_value_ok(self): attr_info = { 'key': { 'allow_post': True, 'default': constants.ATTR_NOT_SPECIFIED, }, } attr_inst = attributes.AttributeInfo(attr_info) self._test_fill_default_value(attr_inst, {'key': 'X'}, {'key': 'X'}) self._test_fill_default_value( attr_inst, {'key': constants.ATTR_NOT_SPECIFIED}, {})
def test_populate_project_id_from_context(self): tenant_id = uuidutils.generate_uuid() ctx = context.Context(user_id=None, tenant_id=tenant_id) # for each create request, the tenant_id should be added to the # req body res_dict = {} attr_inst = attributes.AttributeInfo({}) attr_inst.populate_project_id(ctx, res_dict, is_create=True) self.assertEqual( {'tenant_id': ctx.tenant_id, 'project_id': ctx.tenant_id}, res_dict)
def test_populate_project_id_mandatory_not_specified(self): tenant_id = uuidutils.generate_uuid() ctx = context.Context(user_id=None, tenant_id=tenant_id) # if the tenant_id is mandatory for the resource and not specified # in the request nor in the context, an exception should be raised res_dict = {} attr_info = {'tenant_id': {'allow_post': True}} ctx.tenant_id = None attr_inst = attributes.AttributeInfo(attr_info) self.assertRaises(exc.HTTPBadRequest, attr_inst.populate_project_id, ctx, res_dict, True)
def test_populate_project_id_admin_req(self): tenant_id_1 = uuidutils.generate_uuid() tenant_id_2 = uuidutils.generate_uuid() # non-admin users can't create a res on behalf of another project ctx = context.Context(user_id=None, tenant_id=tenant_id_1) res_dict = {'tenant_id': tenant_id_2} attr_inst = attributes.AttributeInfo({}) self.assertRaises(exc.HTTPBadRequest, attr_inst.populate_project_id, ctx, res_dict, None) # but admin users can ctx.is_admin = True attr_inst.populate_project_id(ctx, res_dict, is_create=False)
def test_fill_no_default_value_no_allow_post(self): attr_info = { 'key': { 'allow_post': False, }, } attr_inst = attributes.AttributeInfo(attr_info) self.assertRaises(exceptions.InvalidInput, self._test_fill_default_value, attr_inst, {'key': 'X'}, {'key': 'X'}) self._test_fill_default_value(attr_inst, {}, {}) self.assertRaises(self._EXC_CLS, attr_inst.fill_post_defaults, {'key': 'X'}, self._EXC_CLS)
def _fixup_res_dict(context, attr_name, res_dict, check_allow_post=True): attr_info = attributes.RESOURCE_ATTRIBUTE_MAP[attr_name] attr_ops = lib_attrs.AttributeInfo(attr_info) try: attr_ops.populate_project_id(context, res_dict, True) lib_attrs.populate_project_info(attr_info) attr_ops.verify_attributes(res_dict) except webob.exc.HTTPBadRequest as e: # convert webob exception into ValueError as these functions are # for internal use. webob exception doesn't make sense. raise ValueError(e.detail) attr_ops.fill_post_defaults(res_dict, check_allow_post=check_allow_post) attr_ops.convert_values(res_dict) return res_dict
def _fixup_res_dict(context, attr_name, res_dict, check_allow_post=True): # This method is a replacement of _fixup_res_dict which is used in # neutron.plugin.common.utils. All this mock does is insert a uuid # for the id field if one is not found ONLY if running in api_replay_mode. if cfg.CONF.api_replay_mode and 'id' not in res_dict: res_dict['id'] = uuidutils.generate_uuid() attr_info = attributes.RESOURCE_ATTRIBUTE_MAP[attr_name] attr_ops = lib_attrs.AttributeInfo(attr_info) try: attr_ops.populate_project_id(context, res_dict, True) lib_attrs.populate_project_info(attr_info) attr_ops.verify_attributes(res_dict) except webob.exc.HTTPBadRequest as e: # convert webob exception into ValueError as these functions are # for internal use. webob exception doesn't make sense. raise ValueError(e.detail) attr_ops.fill_post_defaults(res_dict, check_allow_post=check_allow_post) attr_ops.convert_values(res_dict) return res_dict
from neutron.api.v2 import resource_helper from networking_sfc._i18n import _ from networking_sfc import extensions as sfc_extensions cfg.CONF.import_opt('api_extensions_path', 'neutron.common.config') neutron_ext.append_api_extensions_path(sfc_extensions.__path__) FLOW_CLASSIFIER_EXT = "flow_classifier" FLOW_CLASSIFIER_PREFIX = "/sfc" fc_supported_protocols = [ const.PROTO_NAME_TCP, const.PROTO_NAME_UDP, const.PROTO_NAME_ICMP ] fc_supported_ethertypes = ['IPv4', 'IPv6'] SUPPORTED_L7_PARAMETERS = {} _l7_param_attrs = attr.AttributeInfo(SUPPORTED_L7_PARAMETERS) # Flow Classifier Exceptions class FlowClassifierNotFound(neutron_exc.NotFound): message = _("Flow Classifier %(id)s not found.") class FlowClassifierPortNotFound(neutron_exc.NotFound): message = _("Flow Classifier Neutron Port %(id)s not found.") class FlowClassifierInvalidPortRange(neutron_exc.InvalidInput): message = _("Invalid IP protocol port range. min_port_range=" "%(port_range_min)s must be lesser or equal to " "max_port_range=%(port_range_max)s.")
def test_verify_attributes_null(self): attributes.AttributeInfo({}).verify_attributes({})
def test_verify_attributes_ok_with_project_id(self): attributes.AttributeInfo( {'tenant_id': 'foo', 'project_id': 'foo'}).verify_attributes( {'tenant_id': 'foo'})
def test_create_from_api_def(self): self.assertEqual( port.RESOURCE_ATTRIBUTE_MAP, attributes.AttributeInfo(port.RESOURCE_ATTRIBUTE_MAP).attributes)
def test_create_from_attribute_info_instance(self): cloned_attrs = attributes.AttributeInfo( TestAttributeInfo._ATTRS_INSTANCE) self.assertEqual(TestAttributeInfo._ATTRS_INSTANCE.attributes, cloned_attrs.attributes)
class TestAttributeInfo(base.BaseTestCase): class _MyException(Exception): pass _EXC_CLS = _MyException _RESOURCE_NAME = 'thing' _RESOURCE_ATTRS = {'name': {}, 'type': {}} _RESOURCE_MAP = {_RESOURCE_NAME: _RESOURCE_ATTRS} _ATTRS_INSTANCE = attributes.AttributeInfo(_RESOURCE_MAP) def test_create_from_attribute_info_instance(self): cloned_attrs = attributes.AttributeInfo( TestAttributeInfo._ATTRS_INSTANCE) self.assertEqual(TestAttributeInfo._ATTRS_INSTANCE.attributes, cloned_attrs.attributes) def test_create_from_api_def(self): self.assertEqual( port.RESOURCE_ATTRIBUTE_MAP, attributes.AttributeInfo(port.RESOURCE_ATTRIBUTE_MAP).attributes) def _test_fill_default_value(self, attr_inst, expected, res_dict, check_allow_post=True): attr_inst.fill_post_defaults( res_dict, check_allow_post=check_allow_post) self.assertEqual(expected, res_dict) def test_fill_default_value_ok(self): attr_info = { 'key': { 'allow_post': True, 'default': constants.ATTR_NOT_SPECIFIED, }, } attr_inst = attributes.AttributeInfo(attr_info) self._test_fill_default_value(attr_inst, {'key': 'X'}, {'key': 'X'}) self._test_fill_default_value( attr_inst, {'key': constants.ATTR_NOT_SPECIFIED}, {}) def test_override_no_allow_post(self): attr_info = { 'key': { 'allow_post': False, 'default': constants.ATTR_NOT_SPECIFIED, }, } attr_inst = attributes.AttributeInfo(attr_info) self._test_fill_default_value(attr_inst, {'key': 'X'}, {'key': 'X'}, check_allow_post=False) def test_fill_no_default_value_allow_post(self): attr_info = { 'key': { 'allow_post': True, }, } attr_inst = attributes.AttributeInfo(attr_info) self._test_fill_default_value(attr_inst, {'key': 'X'}, {'key': 'X'}) self.assertRaises(exceptions.InvalidInput, self._test_fill_default_value, attr_inst, {'key': 'X'}, {}) self.assertRaises(self._EXC_CLS, attr_inst.fill_post_defaults, {}, self._EXC_CLS) def test_fill_no_default_value_no_allow_post(self): attr_info = { 'key': { 'allow_post': False, }, } attr_inst = attributes.AttributeInfo(attr_info) self.assertRaises(exceptions.InvalidInput, self._test_fill_default_value, attr_inst, {'key': 'X'}, {'key': 'X'}) self._test_fill_default_value(attr_inst, {}, {}) self.assertRaises(self._EXC_CLS, attr_inst.fill_post_defaults, {'key': 'X'}, self._EXC_CLS) def _test_convert_value(self, attr_inst, expected, res_dict): attr_inst.convert_values(res_dict) self.assertEqual(expected, res_dict) def test_convert_value(self): attr_info = { 'key': { }, } attr_inst = attributes.AttributeInfo(attr_info) self._test_convert_value(attr_inst, {'key': constants.ATTR_NOT_SPECIFIED}, {'key': constants.ATTR_NOT_SPECIFIED}) self._test_convert_value(attr_inst, {'key': 'X'}, {'key': 'X'}) self._test_convert_value(attr_inst, {'other_key': 'X'}, {'other_key': 'X'}) attr_info = { 'key': { 'convert_to': converters.convert_to_int, }, } attr_inst = attributes.AttributeInfo(attr_info) self._test_convert_value(attr_inst, {'key': constants.ATTR_NOT_SPECIFIED}, {'key': constants.ATTR_NOT_SPECIFIED}) self._test_convert_value(attr_inst, {'key': 1}, {'key': '1'}) self._test_convert_value(attr_inst, {'key': 1}, {'key': 1}) self.assertRaises(exceptions.InvalidInput, self._test_convert_value, attr_inst, {'key': 1}, {'key': 'a'}) attr_info = { 'key': { 'validate': {'type:uuid': None}, }, } attr_inst = attributes.AttributeInfo(attr_info) self._test_convert_value(attr_inst, {'key': constants.ATTR_NOT_SPECIFIED}, {'key': constants.ATTR_NOT_SPECIFIED}) uuid_str = '01234567-1234-1234-1234-1234567890ab' self._test_convert_value(attr_inst, {'key': uuid_str}, {'key': uuid_str}) self.assertRaises(exceptions.InvalidInput, self._test_convert_value, attr_inst, {'key': 1}, {'key': 1}) self.assertRaises(self._EXC_CLS, attr_inst.convert_values, {'key': 1}, self._EXC_CLS) def test_populate_project_id_admin_req(self): tenant_id_1 = uuidutils.generate_uuid() tenant_id_2 = uuidutils.generate_uuid() # non-admin users can't create a res on behalf of another project ctx = context.Context(user_id=None, tenant_id=tenant_id_1) res_dict = {'tenant_id': tenant_id_2} attr_inst = attributes.AttributeInfo({}) self.assertRaises(exc.HTTPBadRequest, attr_inst.populate_project_id, ctx, res_dict, None) # but admin users can ctx.is_admin = True attr_inst.populate_project_id(ctx, res_dict, is_create=False) def test_populate_project_id_from_context(self): tenant_id = uuidutils.generate_uuid() ctx = context.Context(user_id=None, tenant_id=tenant_id) # for each create request, the tenant_id should be added to the # req body res_dict = {} attr_inst = attributes.AttributeInfo({}) attr_inst.populate_project_id(ctx, res_dict, is_create=True) self.assertEqual( {'tenant_id': ctx.tenant_id, 'project_id': ctx.tenant_id}, res_dict) def test_populate_project_id_mandatory_not_specified(self): tenant_id = uuidutils.generate_uuid() ctx = context.Context(user_id=None, tenant_id=tenant_id) # if the tenant_id is mandatory for the resource and not specified # in the request nor in the context, an exception should be raised res_dict = {} attr_info = {'tenant_id': {'allow_post': True}} ctx.tenant_id = None attr_inst = attributes.AttributeInfo(attr_info) self.assertRaises(exc.HTTPBadRequest, attr_inst.populate_project_id, ctx, res_dict, True) def test_populate_project_id_not_mandatory(self): ctx = context.Context(user_id=None) # if the tenant_id is not mandatory for the resource it should be # OK if it is not in the request. res_dict = {'name': 'test_port'} attr_inst = attributes.AttributeInfo({}) ctx.tenant_id = None attr_inst.populate_project_id(ctx, res_dict, True) def test_verify_attributes_null(self): attributes.AttributeInfo({}).verify_attributes({}) def test_verify_attributes_ok_with_project_id(self): attributes.AttributeInfo( {'tenant_id': 'foo', 'project_id': 'foo'}).verify_attributes( {'tenant_id': 'foo'}) def test_verify_attributes_ok_subset(self): attributes.AttributeInfo( {'attr1': 'foo', 'attr2': 'bar'}).verify_attributes( {'attr1': 'foo'}) def test_verify_attributes_unrecognized(self): with testtools.ExpectedException(exc.HTTPBadRequest) as bad_req: attributes.AttributeInfo( {'attr1': 'foo'}).verify_attributes( {'attr1': 'foo', 'attr2': 'bar'}) self.assertEqual(bad_req.message, "Unrecognized attribute(s) 'attr2'")
def test_verify_attributes_ok_subset(self): attributes.AttributeInfo( {'attr1': 'foo', 'attr2': 'bar'}).verify_attributes( {'attr1': 'foo'})