diff --git a/openstack/image/v2/image.py b/openstack/image/v2/image.py index e58a7b33f..8005ac13b 100644 --- a/openstack/image/v2/image.py +++ b/openstack/image/v2/image.py @@ -301,3 +301,25 @@ class Image(resource.Resource, resource.TagMixin): request.headers.update(headers) return request + + @classmethod + def find(cls, session, name_or_id, ignore_missing=True, **params): + # Do a regular search first (ignoring missing) + result = super(Image, cls).find(session, name_or_id, True, + **params) + + if result: + return result + else: + # Search also in hidden images + params['is_hidden'] = True + data = cls.list(session, **params) + + result = cls._get_one_match(name_or_id, data) + if result is not None: + return result + + if ignore_missing: + return None + raise exceptions.ResourceNotFound( + "No %s found for %s" % (cls.__name__, name_or_id)) diff --git a/openstack/tests/unit/image/v2/test_image.py b/openstack/tests/unit/image/v2/test_image.py index 7f506a1fe..ebdbd8899 100644 --- a/openstack/tests/unit/image/v2/test_image.py +++ b/openstack/tests/unit/image/v2/test_image.py @@ -86,12 +86,16 @@ EXAMPLE = { class FakeResponse(object): - def __init__(self, response, status_code=200, headers=None): + def __init__(self, response, status_code=200, headers=None, reason=None): self.body = response self.content = response self.status_code = status_code headers = headers if headers else {'content-type': 'application/json'} self.headers = requests.structures.CaseInsensitiveDict(headers) + if reason: + self.reason = reason + # for the sake of "list" response faking + self.links = [] def json(self): return self.body @@ -108,6 +112,7 @@ class TestImage(base.TestCase): self.sess.post = mock.Mock(return_value=self.resp) self.sess.put = mock.Mock(return_value=FakeResponse({})) self.sess.delete = mock.Mock(return_value=FakeResponse({})) + self.sess.fetch = mock.Mock(return_value=FakeResponse({})) self.sess.default_microversion = None self.sess.retriable_status_codes = None @@ -366,3 +371,30 @@ class TestImage(base.TestCase): self.assertEqual( sorted(value, key=operator.itemgetter('value')), sorted(call_kwargs['json'], key=operator.itemgetter('value'))) + + def test_image_find(self): + sot = image.Image() + + self.sess._get_connection = mock.Mock(return_value=self.cloud) + self.sess.get.side_effect = [ + # First fetch by name + FakeResponse(None, 404, headers={}, reason='dummy'), + # Then list with no results + FakeResponse({'images': []}), + # And finally new list of hidden images with one searched + FakeResponse({'images': [EXAMPLE]}) + + ] + + result = sot.find(self.sess, EXAMPLE['name']) + + self.sess.get.assert_has_calls([ + mock.call('images/' + EXAMPLE['name'], microversion=None), + mock.call('/images', headers={'Accept': 'application/json'}, + microversion=None, params={}), + mock.call('/images', headers={'Accept': 'application/json'}, + microversion=None, params={'os_hidden': True}) + ]) + + self.assertIsInstance(result, image.Image) + self.assertEqual(IDENTIFIER, result.id)