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:
Julien Danjou 2015-08-31 16:03:50 +02:00 committed by Chris Dent
parent c18fa79dc8
commit c39bae37b0
6 changed files with 59 additions and 25 deletions

View File

@ -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

View File

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

View File

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

View File

@ -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"

View File

@ -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()

View File

@ -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):