Translate resource_id to UUID5 format.

Due to wsgi spec issue, webserver and webob decode "%2F" into "/" for PATH_INFO.
Some webserver allows to change this behavior, but webob doesn't.
Until we find a suitable better solution I propose that the client
always translate the resource_id in uuid5 format.

Related-bug: #1500890
Change-Id: Id3df57a54ad35b12e691eff2d4d3c40effe1f808
This commit is contained in:
Mehdi Abaakouk 2016-01-19 08:40:55 +01:00
parent f107392374
commit 415050041b
4 changed files with 41 additions and 5 deletions

View File

@ -13,11 +13,13 @@
import uuid
from gnocchiclient.tests.functional import base
from gnocchiclient import utils
class ResourceClientTest(base.ClientTestBase):
RESOURCE_ID = str(uuid.uuid4())
RESOURCE_ID2 = str(uuid.uuid4())
RAW_RESOURCE_ID2 = str(uuid.uuid4()) + "/foo"
RESOURCE_ID2 = utils.encode_resource_id(RAW_RESOURCE_ID2)
PROJECT_ID = str(uuid.uuid4())
def test_help(self):
@ -123,7 +125,7 @@ class ResourceClientTest(base.ClientTestBase):
result = self.gnocchi(
'resource', params=("create %s -t generic "
"-a project_id:%s"
) % (self.RESOURCE_ID2, self.PROJECT_ID))
) % (self.RAW_RESOURCE_ID2, self.PROJECT_ID))
resource2 = self.details_multiple(result)[0]
self.assertEqual(self.RESOURCE_ID2, resource2["id"])
self.assertEqual(self.PROJECT_ID, resource2["project_id"])

View File

@ -12,8 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import uuid
import pyparsing as pp
import six
uninary_operators = ("not", )
binary_operator = (u">=", u"<=", u"!=", u">", u"<", u"=", u"==", u"eq", u"ne",
@ -26,8 +28,9 @@ null = pp.Regex("None|none|null").setParseAction(pp.replaceWith(None))
boolean = "False|True|false|true"
boolean = pp.Regex(boolean).setParseAction(lambda t: t[0].lower() == "true")
hex_string = lambda n: pp.Word(pp.hexnums, exact=n)
uuid = pp.Combine(hex_string(8) + (pp.Optional("-") + hex_string(4)) * 3 +
pp.Optional("-") + hex_string(12))
uuid_string = pp.Combine(hex_string(8) +
(pp.Optional("-") + hex_string(4)) * 3 +
pp.Optional("-") + hex_string(12))
number = r"[+-]?\d+(:?\.\d*)?(:?[eE][+-]?\d+)?"
number = pp.Regex(number).setParseAction(lambda t: float(t[0]))
identifier = pp.Word(pp.alphas, pp.alphanums + "_")
@ -36,7 +39,7 @@ comparison_term = pp.Forward()
in_list = pp.Group(pp.Suppress('[') +
pp.Optional(pp.delimitedList(comparison_term)) +
pp.Suppress(']'))("list")
comparison_term << (null | boolean | uuid | identifier | number |
comparison_term << (null | boolean | uuid_string | identifier | number |
quoted_string | in_list)
condition = pp.Group(comparison_term + operator + comparison_term)
@ -125,3 +128,24 @@ def dict_to_querystring(objs):
return "&".join(["%s=%s" % (k, v)
for k, v in objs.items()
if v is not None])
# 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 encode_resource_id(value):
try:
try:
return str(uuid.UUID(value))
except ValueError:
if len(value) <= 255:
if six.PY2:
value = value.encode('utf-8')
return str(uuid.uuid5(RESOURCE_ID_NAMESPACE, value))
raise ValueError(
'transformable resource id >255 max allowed characters')
except Exception as e:
raise ValueError(e)

View File

@ -51,6 +51,7 @@ class MetricManager(base.Manager):
self._ensure_metric_is_uuid(metric)
url = self.metric_url + metric
else:
resource_id = utils.encode_resource_id(resource_id)
url = (self.resource_url % resource_id) + metric
return self._get(url).json()
@ -79,6 +80,7 @@ class MetricManager(base.Manager):
raise TypeError("metric_name is required if resource_id is set")
del metric['resource_id']
resource_id = utils.encode_resource_id(resource_id)
metric = {metric_name: metric}
metric = self._post(
self.resource_url % resource_id,
@ -99,6 +101,7 @@ class MetricManager(base.Manager):
self._ensure_metric_is_uuid(metric)
url = self.metric_url + metric
else:
resource_id = utils.encode_resource_id(resource_id)
url = self.resource_url % resource_id + metric
self._delete(url)
@ -117,6 +120,7 @@ class MetricManager(base.Manager):
self._ensure_metric_is_uuid(metric)
url = self.metric_url + metric + "/measures"
else:
resource_id = utils.encode_resource_id(resource_id)
url = self.resource_url % resource_id + metric + "/measures"
return self._post(
url, headers={'Content-Type': "application/json"},
@ -153,6 +157,7 @@ class MetricManager(base.Manager):
self._ensure_metric_is_uuid(metric)
url = self.metric_url + metric + "/measures"
else:
resource_id = utils.encode_resource_id(resource_id)
url = self.resource_url % resource_id + metric + "/measures"
return self._get(url, params=params).json()

View File

@ -14,6 +14,7 @@
from oslo_serialization import jsonutils
from six.moves.urllib import parse as urllib_parse
from gnocchiclient import utils
from gnocchiclient.v1 import base
@ -73,6 +74,7 @@ class ResourceManager(base.Manager):
:type history: bool
"""
history = "/history" if history else ""
resource_id = utils.encode_resource_id(resource_id)
url = self.url + "%s/%s%s" % (resource_type, resource_id, history)
return self._get(url).json()
@ -96,6 +98,7 @@ class ResourceManager(base.Manager):
:type sorts: list of str
"""
qs = _get_pagination_options(details, False, limit, marker, sorts)
resource_id = utils.encode_resource_id(resource_id)
url = "%s%s/%s/history?%s" % (self.url, resource_type, resource_id, qs)
return self._get(url).json()
@ -123,6 +126,7 @@ class ResourceManager(base.Manager):
:type resource: dict
"""
resource_id = utils.encode_resource_id(resource_id)
return self._patch(
self.url + resource_type + "/" + resource_id,
headers={'Content-Type': "application/json"},
@ -134,6 +138,7 @@ class ResourceManager(base.Manager):
:param resource_id: ID of the resource
:type resource_id: str
"""
resource_id = utils.encode_resource_id(resource_id)
self._delete(self.url + "generic/" + resource_id)
def search(self, resource_type="generic", query=None, details=False,