diff --git a/gnocchi/indexer/__init__.py b/gnocchi/indexer/__init__.py index 2cbfd31ad..7d5b7f55b 100644 --- a/gnocchi/indexer/__init__.py +++ b/gnocchi/indexer/__init__.py @@ -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 diff --git a/gnocchi/indexer/sqlalchemy.py b/gnocchi/indexer/sqlalchemy.py index dd5500e18..54bd5e12f 100644 --- a/gnocchi/indexer/sqlalchemy.py +++ b/gnocchi/indexer/sqlalchemy.py @@ -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) diff --git a/gnocchi/rest/__init__.py b/gnocchi/rest/__init__.py index 012e1d06b..001589cb8 100644 --- a/gnocchi/rest/__init__.py +++ b/gnocchi/rest/__init__.py @@ -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) diff --git a/gnocchi/tests/gabbi/gabbits/search.yaml b/gnocchi/tests/gabbi/gabbits/search.yaml index f410df073..2cd2deb62 100644 --- a/gnocchi/tests/gabbi/gabbits/search.yaml +++ b/gnocchi/tests/gabbi/gabbits/search.yaml @@ -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" \ No newline at end of file diff --git a/gnocchi/tests/test_indexer.py b/gnocchi/tests/test_indexer.py index 3ae136fed..642f3b4ee 100644 --- a/gnocchi/tests/test_indexer.py +++ b/gnocchi/tests/test_indexer.py @@ -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() diff --git a/gnocchi/utils.py b/gnocchi/utils.py index ee79a4c24..a266c2ec0 100644 --- a/gnocchi/utils.py +++ b/gnocchi/utils.py @@ -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):