[ResourceTypes] Add ability to pick the latest available resource
In case of several regexp matches which not fail in most cases. Taking the latest resource is more user-friednly and allows to remove hardcode of the resource version. Change-Id: Id82b26634d3e3712ad48eb1134a9464cde991c4b
This commit is contained in:
parent
272349796c
commit
930f5a46dd
@ -13,6 +13,8 @@
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import operator
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from rally.common import logging
|
||||
@ -29,6 +31,9 @@ from rally_openstack.services.storage import block
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
configure = plugin.configure
|
||||
|
||||
|
||||
class OpenStackResourceType(types.ResourceType):
|
||||
"""A base class for OpenStack ResourceTypes plugins with help-methods"""
|
||||
|
||||
@ -50,6 +55,81 @@ class OpenStackResourceType(types.ResourceType):
|
||||
self._clients = osclients.Clients(
|
||||
self._context["users"][0]["credential"])
|
||||
|
||||
def _find_resource(self, resource_spec, resources):
|
||||
"""Return the resource whose name matches the pattern.
|
||||
|
||||
.. note:: This method is a modified version of
|
||||
`rally.task.types.obj_from_name`. The difference is supporting the
|
||||
case of returning the latest version of resource in case of
|
||||
`accurate=False` option.
|
||||
|
||||
:param resource_spec: resource specification to find.
|
||||
Expected keys:
|
||||
|
||||
* name - The exact name of resource to search. If no exact match
|
||||
and value of *accurate* key is False (default behaviour), name
|
||||
will be interpreted as a regexp
|
||||
* regexp - a regexp of resource name to match. If several resources
|
||||
match and value of *accurate* key is False (default behaviour),
|
||||
the latest resource will be returned.
|
||||
:param resources: iterable containing all resources
|
||||
:raises InvalidScenarioArgument: if the pattern does
|
||||
not match anything.
|
||||
|
||||
:returns: resource object mapped to `name` or `regex`
|
||||
"""
|
||||
if "name" in resource_spec:
|
||||
# In a case of pattern string exactly matches resource name
|
||||
matching_exact = [resource for resource in resources
|
||||
if resource.name == resource_spec["name"]]
|
||||
if len(matching_exact) == 1:
|
||||
return matching_exact[0]
|
||||
elif len(matching_exact) > 1:
|
||||
raise exceptions.InvalidScenarioArgument(
|
||||
"%(typename)s with name '%(pattern)s' "
|
||||
"is ambiguous, possible matches "
|
||||
"by id: %(ids)s" % {
|
||||
"typename": self.get_name().title(),
|
||||
"pattern": resource_spec["name"],
|
||||
"ids": ", ".join(map(operator.attrgetter("id"),
|
||||
matching_exact))})
|
||||
if resource_spec.get("accurate", False):
|
||||
raise exceptions.InvalidScenarioArgument(
|
||||
"%(typename)s with name '%(name)s' not found" % {
|
||||
"typename": self.get_name().title(),
|
||||
"name": resource_spec["name"]})
|
||||
# Else look up as regex
|
||||
patternstr = resource_spec["name"]
|
||||
elif "regex" in resource_spec:
|
||||
patternstr = resource_spec["regex"]
|
||||
else:
|
||||
raise exceptions.InvalidScenarioArgument(
|
||||
"%(typename)s 'id', 'name', or 'regex' not found "
|
||||
"in '%(resource_spec)s' " % {
|
||||
"typename": self.get_name().title(),
|
||||
"resource_spec": resource_spec})
|
||||
|
||||
pattern = re.compile(patternstr)
|
||||
matching = [resource for resource in resources
|
||||
if re.search(pattern, resource.name or "")]
|
||||
if not matching:
|
||||
raise exceptions.InvalidScenarioArgument(
|
||||
"%(typename)s with pattern '%(pattern)s' not found" % {
|
||||
"typename": self.get_name().title(),
|
||||
"pattern": pattern.pattern})
|
||||
elif len(matching) > 1:
|
||||
if not resource_spec.get("accurate", False):
|
||||
return sorted(matching, key=lambda o: o.name or "")[-1]
|
||||
|
||||
raise exceptions.InvalidScenarioArgument(
|
||||
"%(typename)s with name '%(pattern)s' is ambiguous, possible "
|
||||
"matches by id: %(ids)s" % {
|
||||
"typename": self.get_name().title(),
|
||||
"pattern": pattern.pattern,
|
||||
"ids": ", ".join(map(operator.attrgetter("id"),
|
||||
matching))})
|
||||
return matching[0]
|
||||
|
||||
if rally_openstack.__rally_version__ < (0, 12):
|
||||
@classmethod
|
||||
def _get_doc(cls):
|
||||
@ -121,10 +201,8 @@ class GlanceImage(DeprecatedBehaviourMixin, OpenStackResourceType):
|
||||
glance = image.Image(self._clients)
|
||||
self._cache[cache_id] = glance.list_images(**list_kwargs)
|
||||
images = self._cache[cache_id]
|
||||
resource_id = types._id_from_name(
|
||||
resource_config=resource_spec,
|
||||
resources=images,
|
||||
typename="image")
|
||||
resource = self._find_resource(resource_spec, images)
|
||||
return resource.id
|
||||
return resource_id
|
||||
|
||||
|
||||
|
@ -21,6 +21,96 @@ from tests.unit import fakes
|
||||
from tests.unit import test
|
||||
|
||||
|
||||
class OpenStackResourceTypeTestCase(test.TestCase):
|
||||
def test__find_resource(self):
|
||||
|
||||
@types.configure(name=self.id())
|
||||
class FooType(types.OpenStackResourceType):
|
||||
def pre_process(self, resource_spec, config):
|
||||
pass
|
||||
|
||||
ftype = FooType({})
|
||||
|
||||
resources = dict(
|
||||
(name, fakes.FakeResource(name=name))
|
||||
for name in ["Fake1", "Fake2", "Fake3"])
|
||||
# case #1: 100% name match
|
||||
self.assertEqual(
|
||||
resources["Fake2"],
|
||||
ftype._find_resource({"name": "Fake2"}, resources.values()))
|
||||
|
||||
# case #2: pick the latest one
|
||||
self.assertEqual(
|
||||
resources["Fake3"],
|
||||
ftype._find_resource({"name": "Fake"}, resources.values()))
|
||||
|
||||
# case #3: regex one match
|
||||
self.assertEqual(
|
||||
resources["Fake2"],
|
||||
ftype._find_resource({"regex": ".ake2"}, resources.values()))
|
||||
|
||||
# case #4: regex, pick the latest one
|
||||
self.assertEqual(
|
||||
resources["Fake3"],
|
||||
ftype._find_resource({"regex": "Fake"}, resources.values()))
|
||||
|
||||
def test__find_resource_negative(self):
|
||||
|
||||
@types.configure(name=self.id())
|
||||
class FooType(types.OpenStackResourceType):
|
||||
def pre_process(self, resource_spec, config):
|
||||
pass
|
||||
|
||||
ftype = FooType({})
|
||||
# case #1: the wrong resource spec
|
||||
e = self.assertRaises(exceptions.InvalidScenarioArgument,
|
||||
ftype._find_resource, {}, [])
|
||||
self.assertIn("'id', 'name', or 'regex' not found",
|
||||
e.format_message())
|
||||
|
||||
# case #2: two matches for one name
|
||||
resources = [fakes.FakeResource(name="Fake1"),
|
||||
fakes.FakeResource(name="Fake2"),
|
||||
fakes.FakeResource(name="Fake1")]
|
||||
e = self.assertRaises(
|
||||
exceptions.InvalidScenarioArgument,
|
||||
ftype._find_resource, {"name": "Fake1"}, resources)
|
||||
self.assertIn("with name 'Fake1' is ambiguous, possible matches",
|
||||
e.format_message())
|
||||
|
||||
# case #3: no matches at all
|
||||
resources = [fakes.FakeResource(name="Fake1"),
|
||||
fakes.FakeResource(name="Fake2"),
|
||||
fakes.FakeResource(name="Fake3")]
|
||||
e = self.assertRaises(
|
||||
exceptions.InvalidScenarioArgument,
|
||||
ftype._find_resource, {"name": "Foo"}, resources)
|
||||
self.assertIn("with pattern 'Foo' not found",
|
||||
e.format_message())
|
||||
|
||||
# case #4: two matches for one name, but 'accurate' is True
|
||||
resources = [fakes.FakeResource(name="Fake1"),
|
||||
fakes.FakeResource(name="Fake2"),
|
||||
fakes.FakeResource(name="Fake3")]
|
||||
e = self.assertRaises(
|
||||
exceptions.InvalidScenarioArgument,
|
||||
ftype._find_resource, {"name": "Fake", "accurate": True},
|
||||
resources)
|
||||
self.assertIn("with name 'Fake' not found",
|
||||
e.format_message())
|
||||
|
||||
# case #5: two matches for one name, but 'accurate' is True
|
||||
resources = [fakes.FakeResource(name="Fake1"),
|
||||
fakes.FakeResource(name="Fake2"),
|
||||
fakes.FakeResource(name="Fake3")]
|
||||
e = self.assertRaises(
|
||||
exceptions.InvalidScenarioArgument,
|
||||
ftype._find_resource, {"regex": "Fake", "accurate": True},
|
||||
resources)
|
||||
self.assertIn("with name 'Fake' is ambiguous, possible matches",
|
||||
e.format_message())
|
||||
|
||||
|
||||
class FlavorTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -168,9 +258,11 @@ class GlanceImageTestCase(test.TestCase):
|
||||
|
||||
def test_preprocess_by_regex_match_multiple(self):
|
||||
resource_spec = {"regex": "^cirros"}
|
||||
self.assertRaises(exceptions.InvalidScenarioArgument,
|
||||
self.type_cls.pre_process,
|
||||
resource_spec=resource_spec, config={})
|
||||
image_id = self.type_cls.pre_process(resource_spec=resource_spec,
|
||||
config={})
|
||||
# matching resources are sorted by the names. It is impossible to
|
||||
# predict which resource will be luckiest
|
||||
self.assertIn(image_id, ["102", "103"])
|
||||
|
||||
def test_preprocess_by_regex_no_match(self):
|
||||
resource_spec = {"regex": "-boot$"}
|
||||
|
Loading…
Reference in New Issue
Block a user