From 5e09afc80e6bffdb8b63b51cc6b76efdcbf9481b Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Thu, 28 Feb 2019 12:12:04 +0100 Subject: [PATCH] Tweak find_image method to search in hidden images With Image v2.7 a possibility of hiding and image is being added (an attribute 'os_hidden'). After an image is being hidden the only possibility to find image by name is to do a list with `os_hidden=True` to list hidden images. So what we do here is search as usual, and if nothing found try also to search in hidden images (list of hidden images and grep by name) Change-Id: I237da3e7e6b5d23010d8b26a3fcbfabaddeb5f31 --- openstack/image/v2/image.py | 22 +++++++++++++ openstack/tests/unit/image/v2/test_image.py | 34 ++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) 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)