sqlalchemy: convert UUID correctly before doing a search
Currently UUID are passed as string to SQLAlchemy, but that ends up failing in a DBError if the RDBMS cannot transform a random string to a valid UUID. We now convert the value to a ResourceUUID, except if it fails, where we returns a 400 error. Change-Id: I17adb24ffd169cba6606314c7de7f62b409d4fb4 Closes-Bug: #1490553
This commit is contained in:
parent
c18fa79dc8
commit
c39bae37b0
|
@ -187,6 +187,12 @@ class QueryError(IndexerException):
|
|||
super(QueryError, self).__init__("Unable to parse this query")
|
||||
|
||||
|
||||
class QueryValueError(QueryError, ValueError):
|
||||
def __init__(self, v, f):
|
||||
super(QueryError, self).__init__("Invalid value: `%s' for field `%s'"
|
||||
% (v, f))
|
||||
|
||||
|
||||
class QueryInvalidOperator(QueryError):
|
||||
def __init__(self, op):
|
||||
self.op = op
|
||||
|
|
|
@ -25,6 +25,7 @@ from oslo_db.sqlalchemy import session
|
|||
from oslo_db.sqlalchemy import utils as oslo_db_utils
|
||||
import six
|
||||
import sqlalchemy
|
||||
import sqlalchemy_utils
|
||||
from stevedore import extension
|
||||
|
||||
from gnocchi import exceptions
|
||||
|
@ -618,9 +619,15 @@ class QueryTransformer(object):
|
|||
raise indexer.QueryAttributeError(table, field_name)
|
||||
|
||||
# Convert value to the right type
|
||||
if value is not None and isinstance(attr.type,
|
||||
base.PreciseTimestamp):
|
||||
value = utils.to_timestamp(value)
|
||||
if value is not None:
|
||||
if isinstance(attr.type, base.PreciseTimestamp):
|
||||
value = utils.to_timestamp(value)
|
||||
elif (isinstance(attr.type, sqlalchemy_utils.UUIDType)
|
||||
and not isinstance(value, uuid.UUID)):
|
||||
try:
|
||||
value = utils.ResourceUUID(value)
|
||||
except Exception:
|
||||
raise indexer.QueryValueError(value, field_name)
|
||||
|
||||
return op(attr, value)
|
||||
|
||||
|
|
|
@ -34,11 +34,6 @@ from gnocchi import json
|
|||
from gnocchi import storage
|
||||
from gnocchi import utils
|
||||
|
||||
# uuid5 namespace for id transformation.
|
||||
# NOTE(chdent): This UUID must stay the same, forever, across all
|
||||
# of gnocchi to preserve its value as a URN namespace.
|
||||
RESOURCE_ID_NAMESPACE = uuid.UUID('0a7a15ff-aa13-4ac2-897c-9bdf30ce175b')
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -514,21 +509,6 @@ class MetricController(rest.RestController):
|
|||
abort(404, e)
|
||||
|
||||
|
||||
def ResourceUUID(value):
|
||||
try:
|
||||
try:
|
||||
return uuid.UUID(value)
|
||||
except ValueError:
|
||||
if len(value) <= 255:
|
||||
if six.PY2:
|
||||
value = value.encode('utf-8')
|
||||
return uuid.uuid5(RESOURCE_ID_NAMESPACE, value)
|
||||
raise ValueError(
|
||||
'transformable resource id >255 max allowed characters')
|
||||
except Exception as e:
|
||||
raise ValueError(e)
|
||||
|
||||
|
||||
def UUID(value):
|
||||
try:
|
||||
return uuid.UUID(value)
|
||||
|
@ -769,7 +749,7 @@ def etag_set_headers(obj):
|
|||
|
||||
def ResourceSchema(schema):
|
||||
base_schema = {
|
||||
"id": ResourceUUID,
|
||||
"id": utils.ResourceUUID,
|
||||
voluptuous.Optional('started_at'): Timestamp,
|
||||
voluptuous.Optional('ended_at'): Timestamp,
|
||||
voluptuous.Optional('user_id'): voluptuous.Any(None, UUID),
|
||||
|
@ -787,7 +767,7 @@ class GenericResourceController(rest.RestController):
|
|||
|
||||
def __init__(self, id):
|
||||
try:
|
||||
self.id = ResourceUUID(id)
|
||||
self.id = utils.ResourceUUID(id)
|
||||
except ValueError:
|
||||
abort(404)
|
||||
self.metric = NamedMetricController(str(self.id), self._resource_type)
|
||||
|
|
|
@ -14,3 +14,12 @@ tests:
|
|||
- name: typo of search in resource
|
||||
url: /v1/search/resource/foobar
|
||||
status: 404
|
||||
|
||||
- name: search with invalid uuid
|
||||
url: /v1/search/resource/generic
|
||||
method: POST
|
||||
request_headers:
|
||||
content-type: application/json
|
||||
data:
|
||||
=:
|
||||
id: "cd9eef"
|
|
@ -624,6 +624,16 @@ class TestIndexerDriver(tests_base.TestCase):
|
|||
else:
|
||||
self.fail("Some resources were not found")
|
||||
|
||||
def test_list_resource_weird_uuid(self):
|
||||
r = self.index.list_resources(
|
||||
'generic', attribute_filter={"=": {"id": "f00bar"}})
|
||||
self.assertEqual(0, len(r))
|
||||
self.assertRaises(
|
||||
indexer.QueryValueError,
|
||||
self.index.list_resources,
|
||||
'generic',
|
||||
attribute_filter={"=": {"id": "f00bar" * 50}})
|
||||
|
||||
def test_list_resources_without_history(self):
|
||||
e = uuid.uuid4()
|
||||
rid = uuid.uuid4()
|
||||
|
|
|
@ -19,6 +19,28 @@ import iso8601
|
|||
from oslo_utils import timeutils
|
||||
from pytimeparse import timeparse
|
||||
import six
|
||||
import uuid
|
||||
|
||||
|
||||
# uuid5 namespace for id transformation.
|
||||
# NOTE(chdent): This UUID must stay the same, forever, across all
|
||||
# of gnocchi to preserve its value as a URN namespace.
|
||||
RESOURCE_ID_NAMESPACE = uuid.UUID('0a7a15ff-aa13-4ac2-897c-9bdf30ce175b')
|
||||
|
||||
|
||||
def ResourceUUID(value):
|
||||
try:
|
||||
try:
|
||||
return uuid.UUID(value)
|
||||
except ValueError:
|
||||
if len(value) <= 255:
|
||||
if six.PY2:
|
||||
value = value.encode('utf-8')
|
||||
return uuid.uuid5(RESOURCE_ID_NAMESPACE, value)
|
||||
raise ValueError(
|
||||
'transformable resource id >255 max allowed characters')
|
||||
except Exception as e:
|
||||
raise ValueError(e)
|
||||
|
||||
|
||||
def to_timestamp(v):
|
||||
|
|
Loading…
Reference in New Issue