use attribute functions/operations from neutron-lib
neutron-lib now contains the functionality from neutron's attributes module. This patch moves the local references over to neturon-lib's implementation and removes the functionality from the attributes module. NeutronLibImpact Change-Id: I773702ebd4b66d28059cb3ced64b2b477ceff8af
This commit is contained in:
parent
05c22d6199
commit
cbb0970b36
|
@ -14,12 +14,8 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from neutron_lib.api import converters as lib_converters
|
from neutron_lib.api import converters as lib_converters
|
||||||
from neutron_lib.api import validators as lib_validators
|
|
||||||
from neutron_lib import constants
|
from neutron_lib import constants
|
||||||
from neutron_lib.db import constants as db_const
|
from neutron_lib.db import constants as db_const
|
||||||
import webob.exc
|
|
||||||
|
|
||||||
from neutron._i18n import _
|
|
||||||
|
|
||||||
|
|
||||||
# Defining a constant to avoid repeating string literal in several modules
|
# Defining a constant to avoid repeating string literal in several modules
|
||||||
|
@ -289,9 +285,6 @@ RESOURCE_FOREIGN_KEYS = {
|
||||||
NETWORKS: 'network_id'
|
NETWORKS: 'network_id'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Removing PLURALS breaks subprojects, but they don't need it so they
|
|
||||||
# need to be patched.
|
|
||||||
|
|
||||||
|
|
||||||
def get_collection_info(collection):
|
def get_collection_info(collection):
|
||||||
"""Helper function to retrieve attribute info.
|
"""Helper function to retrieve attribute info.
|
||||||
|
@ -299,106 +292,3 @@ def get_collection_info(collection):
|
||||||
:param collection: Collection or plural name of the resource
|
:param collection: Collection or plural name of the resource
|
||||||
"""
|
"""
|
||||||
return RESOURCE_ATTRIBUTE_MAP.get(collection)
|
return RESOURCE_ATTRIBUTE_MAP.get(collection)
|
||||||
|
|
||||||
|
|
||||||
def fill_default_value(attr_info, res_dict,
|
|
||||||
exc_cls=ValueError,
|
|
||||||
check_allow_post=True):
|
|
||||||
for attr, attr_vals in attr_info.items():
|
|
||||||
if attr_vals['allow_post']:
|
|
||||||
if 'default' not in attr_vals and attr not in res_dict:
|
|
||||||
msg = _("Failed to parse request. Required "
|
|
||||||
"attribute '%s' not specified") % attr
|
|
||||||
raise exc_cls(msg)
|
|
||||||
res_dict[attr] = res_dict.get(attr,
|
|
||||||
attr_vals.get('default'))
|
|
||||||
elif check_allow_post:
|
|
||||||
if attr in res_dict:
|
|
||||||
msg = _("Attribute '%s' not allowed in POST") % attr
|
|
||||||
raise exc_cls(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_value(attr_info, res_dict, exc_cls=ValueError):
|
|
||||||
for attr, attr_vals in attr_info.items():
|
|
||||||
if (attr not in res_dict or
|
|
||||||
res_dict[attr] is constants.ATTR_NOT_SPECIFIED):
|
|
||||||
continue
|
|
||||||
# Convert values if necessary
|
|
||||||
if 'convert_to' in attr_vals:
|
|
||||||
res_dict[attr] = attr_vals['convert_to'](res_dict[attr])
|
|
||||||
# Check that configured values are correct
|
|
||||||
if 'validate' not in attr_vals:
|
|
||||||
continue
|
|
||||||
for rule in attr_vals['validate']:
|
|
||||||
validator = lib_validators.get_validator(rule)
|
|
||||||
res = validator(res_dict[attr], attr_vals['validate'][rule])
|
|
||||||
|
|
||||||
if res:
|
|
||||||
msg_dict = dict(attr=attr, reason=res)
|
|
||||||
msg = _("Invalid input for %(attr)s. "
|
|
||||||
"Reason: %(reason)s.") % msg_dict
|
|
||||||
raise exc_cls(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def populate_project_info(attributes):
|
|
||||||
"""
|
|
||||||
Ensure that both project_id and tenant_id attributes are present.
|
|
||||||
|
|
||||||
If either project_id or tenant_id is present in attributes then ensure
|
|
||||||
that both are present.
|
|
||||||
|
|
||||||
If neither are present then attributes is not updated.
|
|
||||||
|
|
||||||
:param attributes: a dictionary of resource/API attributes
|
|
||||||
:type attributes: dict
|
|
||||||
|
|
||||||
:return: the updated attributes dictionary
|
|
||||||
:rtype: dict
|
|
||||||
"""
|
|
||||||
if 'tenant_id' in attributes and 'project_id' not in attributes:
|
|
||||||
# TODO(HenryG): emit a deprecation warning here
|
|
||||||
attributes['project_id'] = attributes['tenant_id']
|
|
||||||
elif 'project_id' in attributes and 'tenant_id' not in attributes:
|
|
||||||
# Backward compatibility for code still using tenant_id
|
|
||||||
attributes['tenant_id'] = attributes['project_id']
|
|
||||||
|
|
||||||
if attributes.get('project_id') != attributes.get('tenant_id'):
|
|
||||||
msg = _("'project_id' and 'tenant_id' do not match")
|
|
||||||
raise webob.exc.HTTPBadRequest(msg)
|
|
||||||
|
|
||||||
return attributes
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_privileges(context, res_dict):
|
|
||||||
if ('project_id' in res_dict and
|
|
||||||
res_dict['project_id'] != context.project_id and
|
|
||||||
not context.is_admin):
|
|
||||||
msg = _("Specifying 'project_id' or 'tenant_id' other than "
|
|
||||||
"authenticated project in request requires admin privileges")
|
|
||||||
raise webob.exc.HTTPBadRequest(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def populate_tenant_id(context, res_dict, attr_info, is_create):
|
|
||||||
populate_project_info(res_dict)
|
|
||||||
_validate_privileges(context, res_dict)
|
|
||||||
|
|
||||||
if is_create and 'project_id' not in res_dict:
|
|
||||||
if context.project_id:
|
|
||||||
res_dict['project_id'] = context.project_id
|
|
||||||
|
|
||||||
# For backward compatibility
|
|
||||||
res_dict['tenant_id'] = context.project_id
|
|
||||||
|
|
||||||
elif 'tenant_id' in attr_info:
|
|
||||||
msg = _("Running without keystone AuthN requires "
|
|
||||||
"that tenant_id is specified")
|
|
||||||
raise webob.exc.HTTPBadRequest(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def verify_attributes(res_dict, attr_info):
|
|
||||||
populate_project_info(attr_info)
|
|
||||||
|
|
||||||
extra_keys = set(res_dict.keys()) - set(attr_info.keys())
|
|
||||||
if extra_keys:
|
|
||||||
msg = _("Unrecognized attribute(s) '%s'") % ', '.join(extra_keys)
|
|
||||||
raise webob.exc.HTTPBadRequest(msg)
|
|
||||||
|
|
|
@ -17,18 +17,17 @@ import collections
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
from neutron_lib.api import attributes
|
||||||
from neutron_lib.callbacks import events
|
from neutron_lib.callbacks import events
|
||||||
from neutron_lib.callbacks import registry
|
from neutron_lib.callbacks import registry
|
||||||
from neutron_lib import exceptions
|
from neutron_lib import exceptions
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_policy import policy as oslo_policy
|
from oslo_policy import policy as oslo_policy
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
from oslo_utils import strutils
|
|
||||||
import webob.exc
|
import webob.exc
|
||||||
|
|
||||||
from neutron._i18n import _, _LE, _LI
|
from neutron._i18n import _, _LE, _LI
|
||||||
from neutron.api import api_common
|
from neutron.api import api_common
|
||||||
from neutron.api.v2 import attributes
|
|
||||||
from neutron.api.v2 import resource as wsgi_resource
|
from neutron.api.v2 import resource as wsgi_resource
|
||||||
from neutron.common import constants as n_const
|
from neutron.common import constants as n_const
|
||||||
from neutron.common import exceptions as n_exc
|
from neutron.common import exceptions as n_exc
|
||||||
|
@ -693,8 +692,7 @@ class Controller(object):
|
||||||
if not body:
|
if not body:
|
||||||
raise webob.exc.HTTPBadRequest(_("Resource body required"))
|
raise webob.exc.HTTPBadRequest(_("Resource body required"))
|
||||||
|
|
||||||
LOG.debug("Request body: %(body)s",
|
LOG.debug("Request body: %(body)s", {'body': body})
|
||||||
{'body': strutils.mask_password(body)})
|
|
||||||
try:
|
try:
|
||||||
if collection in body:
|
if collection in body:
|
||||||
if not allow_bulk:
|
if not allow_bulk:
|
||||||
|
@ -717,19 +715,21 @@ class Controller(object):
|
||||||
msg = _("Unable to find '%s' in request body") % resource
|
msg = _("Unable to find '%s' in request body") % resource
|
||||||
raise webob.exc.HTTPBadRequest(msg)
|
raise webob.exc.HTTPBadRequest(msg)
|
||||||
|
|
||||||
attributes.populate_tenant_id(context, res_dict, attr_info, is_create)
|
attr_ops = attributes.AttributeInfo(attr_info)
|
||||||
attributes.verify_attributes(res_dict, 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
|
if is_create: # POST
|
||||||
attributes.fill_default_value(attr_info, res_dict,
|
attr_ops.fill_post_defaults(
|
||||||
webob.exc.HTTPBadRequest)
|
res_dict, exc_cls=webob.exc.HTTPBadRequest)
|
||||||
else: # PUT
|
else: # PUT
|
||||||
for attr, attr_vals in attr_info.items():
|
for attr, attr_vals in attr_info.items():
|
||||||
if attr in res_dict and not attr_vals['allow_put']:
|
if attr in res_dict and not attr_vals['allow_put']:
|
||||||
msg = _("Cannot update read-only attribute %s") % attr
|
msg = _("Cannot update read-only attribute %s") % attr
|
||||||
raise webob.exc.HTTPBadRequest(msg)
|
raise webob.exc.HTTPBadRequest(msg)
|
||||||
|
|
||||||
attributes.convert_value(attr_info, res_dict, webob.exc.HTTPBadRequest)
|
attr_ops.convert_values(res_dict, exc_cls=webob.exc.HTTPBadRequest)
|
||||||
return body
|
return body
|
||||||
|
|
||||||
def _validate_network_tenant_ownership(self, request, resource_item):
|
def _validate_network_tenant_ownership(self, request, resource_item):
|
||||||
|
|
|
@ -16,12 +16,12 @@ NOTE: This module shall not be used by external projects. It will be moved
|
||||||
to neutron-lib in due course, and then it can be used from there.
|
to neutron-lib in due course, and then it can be used from there.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from neutron_lib.api import attributes
|
||||||
from neutron_lib.db import utils as db_utils
|
from neutron_lib.db import utils as db_utils
|
||||||
from oslo_db.sqlalchemy import utils as sa_utils
|
from oslo_db.sqlalchemy import utils as sa_utils
|
||||||
from sqlalchemy import sql, or_, and_
|
from sqlalchemy import sql, or_, and_
|
||||||
from sqlalchemy.ext import associationproxy
|
from sqlalchemy.ext import associationproxy
|
||||||
|
|
||||||
from neutron.api.v2 import attributes
|
|
||||||
from neutron.common import utils
|
from neutron.common import utils
|
||||||
from neutron.db import _utils as ndb_utils
|
from neutron.db import _utils as ndb_utils
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,12 @@ NOTE: This module shall not be used by external projects. It will be moved
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
|
from neutron_lib.api import attributes
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
from sqlalchemy.ext import associationproxy
|
from sqlalchemy.ext import associationproxy
|
||||||
|
|
||||||
from neutron._i18n import _LE
|
from neutron._i18n import _LE
|
||||||
from neutron.api.v2 import attributes
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import collections
|
||||||
import contextlib
|
import contextlib
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
|
from neutron_lib.api import attributes as lib_attrs
|
||||||
from neutron_lib import constants as n_const
|
from neutron_lib import constants as n_const
|
||||||
from neutron_lib import exceptions
|
from neutron_lib import exceptions
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
@ -150,16 +151,17 @@ def in_pending_status(status):
|
||||||
|
|
||||||
def _fixup_res_dict(context, attr_name, res_dict, check_allow_post=True):
|
def _fixup_res_dict(context, attr_name, res_dict, check_allow_post=True):
|
||||||
attr_info = attributes.RESOURCE_ATTRIBUTE_MAP[attr_name]
|
attr_info = attributes.RESOURCE_ATTRIBUTE_MAP[attr_name]
|
||||||
|
attr_ops = lib_attrs.AttributeInfo(attr_info)
|
||||||
try:
|
try:
|
||||||
attributes.populate_tenant_id(context, res_dict, attr_info, True)
|
attr_ops.populate_project_id(context, res_dict, True)
|
||||||
attributes.verify_attributes(res_dict, attr_info)
|
lib_attrs.populate_project_info(attr_info)
|
||||||
|
attr_ops.verify_attributes(res_dict)
|
||||||
except webob.exc.HTTPBadRequest as e:
|
except webob.exc.HTTPBadRequest as e:
|
||||||
# convert webob exception into ValueError as these functions are
|
# convert webob exception into ValueError as these functions are
|
||||||
# for internal use. webob exception doesn't make sense.
|
# for internal use. webob exception doesn't make sense.
|
||||||
raise ValueError(e.detail)
|
raise ValueError(e.detail)
|
||||||
attributes.fill_default_value(attr_info, res_dict,
|
attr_ops.fill_post_defaults(res_dict, check_allow_post=check_allow_post)
|
||||||
check_allow_post=check_allow_post)
|
attr_ops.convert_values(res_dict)
|
||||||
attributes.convert_value(attr_info, res_dict)
|
|
||||||
return res_dict
|
return res_dict
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,135 +13,10 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from neutron_lib.api import converters
|
|
||||||
from neutron_lib import constants
|
|
||||||
from neutron_lib import context
|
|
||||||
from neutron_lib import exceptions as n_exc
|
|
||||||
from oslo_utils import uuidutils
|
|
||||||
import webob.exc
|
|
||||||
|
|
||||||
from neutron.api.v2 import attributes
|
from neutron.api.v2 import attributes
|
||||||
from neutron.tests import base
|
from neutron.tests import base
|
||||||
|
|
||||||
|
|
||||||
class TestResDict(base.BaseTestCase):
|
|
||||||
class _MyException(Exception):
|
|
||||||
pass
|
|
||||||
_EXC_CLS = _MyException
|
|
||||||
|
|
||||||
def _test_fill_default_value(self, attr_info, expected, res_dict):
|
|
||||||
attributes.fill_default_value(attr_info, res_dict)
|
|
||||||
self.assertEqual(expected, res_dict)
|
|
||||||
|
|
||||||
def test_fill_default_value(self):
|
|
||||||
attr_info = {
|
|
||||||
'key': {
|
|
||||||
'allow_post': True,
|
|
||||||
'default': constants.ATTR_NOT_SPECIFIED,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self._test_fill_default_value(attr_info, {'key': 'X'}, {'key': 'X'})
|
|
||||||
self._test_fill_default_value(
|
|
||||||
attr_info, {'key': constants.ATTR_NOT_SPECIFIED}, {})
|
|
||||||
|
|
||||||
attr_info = {
|
|
||||||
'key': {
|
|
||||||
'allow_post': True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self._test_fill_default_value(attr_info, {'key': 'X'}, {'key': 'X'})
|
|
||||||
self.assertRaises(ValueError, self._test_fill_default_value,
|
|
||||||
attr_info, {'key': 'X'}, {})
|
|
||||||
self.assertRaises(self._EXC_CLS, attributes.fill_default_value,
|
|
||||||
attr_info, {}, self._EXC_CLS)
|
|
||||||
attr_info = {
|
|
||||||
'key': {
|
|
||||||
'allow_post': False,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self.assertRaises(ValueError, self._test_fill_default_value,
|
|
||||||
attr_info, {'key': 'X'}, {'key': 'X'})
|
|
||||||
self._test_fill_default_value(attr_info, {}, {})
|
|
||||||
self.assertRaises(self._EXC_CLS, attributes.fill_default_value,
|
|
||||||
attr_info, {'key': 'X'}, self._EXC_CLS)
|
|
||||||
|
|
||||||
def _test_convert_value(self, attr_info, expected, res_dict):
|
|
||||||
attributes.convert_value(attr_info, res_dict)
|
|
||||||
self.assertEqual(expected, res_dict)
|
|
||||||
|
|
||||||
def test_convert_value(self):
|
|
||||||
attr_info = {
|
|
||||||
'key': {
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self._test_convert_value(attr_info,
|
|
||||||
{'key': constants.ATTR_NOT_SPECIFIED},
|
|
||||||
{'key': constants.ATTR_NOT_SPECIFIED})
|
|
||||||
self._test_convert_value(attr_info, {'key': 'X'}, {'key': 'X'})
|
|
||||||
self._test_convert_value(attr_info,
|
|
||||||
{'other_key': 'X'}, {'other_key': 'X'})
|
|
||||||
|
|
||||||
attr_info = {
|
|
||||||
'key': {
|
|
||||||
'convert_to': converters.convert_to_int,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self._test_convert_value(attr_info,
|
|
||||||
{'key': constants.ATTR_NOT_SPECIFIED},
|
|
||||||
{'key': constants.ATTR_NOT_SPECIFIED})
|
|
||||||
self._test_convert_value(attr_info, {'key': 1}, {'key': '1'})
|
|
||||||
self._test_convert_value(attr_info, {'key': 1}, {'key': 1})
|
|
||||||
self.assertRaises(n_exc.InvalidInput, self._test_convert_value,
|
|
||||||
attr_info, {'key': 1}, {'key': 'a'})
|
|
||||||
|
|
||||||
attr_info = {
|
|
||||||
'key': {
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self._test_convert_value(attr_info,
|
|
||||||
{'key': constants.ATTR_NOT_SPECIFIED},
|
|
||||||
{'key': constants.ATTR_NOT_SPECIFIED})
|
|
||||||
uuid_str = '01234567-1234-1234-1234-1234567890ab'
|
|
||||||
self._test_convert_value(attr_info,
|
|
||||||
{'key': uuid_str}, {'key': uuid_str})
|
|
||||||
self.assertRaises(ValueError, self._test_convert_value,
|
|
||||||
attr_info, {'key': 1}, {'key': 1})
|
|
||||||
self.assertRaises(self._EXC_CLS, attributes.convert_value,
|
|
||||||
attr_info, {'key': 1}, self._EXC_CLS)
|
|
||||||
|
|
||||||
def test_populate_tenant_id(self):
|
|
||||||
tenant_id_1 = uuidutils.generate_uuid()
|
|
||||||
tenant_id_2 = uuidutils.generate_uuid()
|
|
||||||
# apart from the admin, nobody can create a res on behalf of another
|
|
||||||
# tenant
|
|
||||||
ctx = context.Context(user_id=None, tenant_id=tenant_id_1)
|
|
||||||
res_dict = {'tenant_id': tenant_id_2}
|
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
||||||
attributes.populate_tenant_id,
|
|
||||||
ctx, res_dict, None, None)
|
|
||||||
ctx.is_admin = True
|
|
||||||
self.assertIsNone(attributes.populate_tenant_id(ctx, res_dict,
|
|
||||||
None, None))
|
|
||||||
|
|
||||||
# for each create request, the tenant_id should be added to the
|
|
||||||
# req body
|
|
||||||
res_dict2 = {}
|
|
||||||
attributes.populate_tenant_id(ctx, res_dict2, None, True)
|
|
||||||
self.assertEqual(
|
|
||||||
{'tenant_id': ctx.tenant_id, 'project_id': ctx.tenant_id},
|
|
||||||
res_dict2)
|
|
||||||
|
|
||||||
# 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_dict3 = {}
|
|
||||||
attr_info = {'tenant_id': {'allow_post': True}, }
|
|
||||||
ctx.tenant_id = None
|
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
||||||
attributes.populate_tenant_id,
|
|
||||||
ctx, res_dict3, attr_info, True)
|
|
||||||
|
|
||||||
|
|
||||||
class TestHelpers(base.DietTestCase):
|
class TestHelpers(base.DietTestCase):
|
||||||
|
|
||||||
def _verify_port_attributes(self, attrs):
|
def _verify_port_attributes(self, attrs):
|
||||||
|
|
Loading…
Reference in New Issue