From e1bb4378db1e5be85a59861c2d473388fa336da8 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Fri, 18 Oct 2019 07:37:19 -0700 Subject: Add images.GlanceManager.find_images() bulk query This adds a bulk query method to GlanceManager that takes multiple image names or ids and attempts to query glance for all of them in fewer requests than one-per-image. Change-Id: I1c6ce27b65920356df6b7001c581a2922765ce0c --- novaclient/tests/unit/v2/fakes.py | 13 ++++++++++-- novaclient/tests/unit/v2/test_shell.py | 24 ++++++++++++++++++++++ novaclient/v2/images.py | 37 ++++++++++++++++++++++++++++++++++ novaclient/v2/shell.py | 8 ++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py index 6302c116..4cb4025e 100644 --- a/novaclient/tests/unit/v2/fakes.py +++ b/novaclient/tests/unit/v2/fakes.py @@ -1141,7 +1141,7 @@ class FakeSessionClient(base_client.SessionClient): # Images # def get_images(self, **kw): - return (200, {}, {'images': [ + images = [ { "id": FAKE_IMAGE_UUID_SNAPSHOT, "name": "My Server Backup", @@ -1191,7 +1191,16 @@ class FakeSessionClient(base_client.SessionClient): "progress": 80, "links": {}, }, - ]}) + ] + + if 'id' in kw: + requested = kw['id'].replace('in:', '').split(',') + images = [i for i in images if i['id'] in requested] + if 'names' in kw: + requested = kw['names'].replace('in:', '').split(',') + images = [i for i in images if i['name'] in requested] + + return (200, {}, {'images': images}) def get_images_555cae93_fb41_4145_9c52_f5b923538a26(self, **kw): return (200, {}, {'image': self.get_images()[2]['images'][0]}) diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index 79118afc..56fa3544 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -4676,3 +4676,27 @@ class PollForStatusTestCase(utils.TestCase): action=action, show_progress=True, silent=False) + + +class TestUtilMethods(utils.TestCase): + def setUp(self): + super(TestUtilMethods, self).setUp() + self.shell = self.useFixture(ShellFixture()).shell + # NOTE(danms): Get a client that we can use to call things outside of + # the shell main + self.shell.cs = fakes.FakeClient('2.1') + + def test_find_images(self): + """Test find_images() with a name and id.""" + images = novaclient.v2.shell._find_images(self.shell.cs, + [FAKE_UUID_1, + 'back1']) + self.assertEqual(2, len(images)) + self.assertEqual(FAKE_UUID_1, images[0].id) + self.assertEqual(fakes.FAKE_IMAGE_UUID_BACKUP, images[1].id) + + def test_find_images_missing(self): + """Test find_images() where one of the images is not found.""" + self.assertRaises(exceptions.CommandError, + novaclient.v2.shell._find_images, + self.shell.cs, [FAKE_UUID_1, 'foo']) diff --git a/novaclient/v2/images.py b/novaclient/v2/images.py index e83d9b2f..0aced5c7 100644 --- a/novaclient/v2/images.py +++ b/novaclient/v2/images.py @@ -66,6 +66,43 @@ class GlanceManager(base.Manager): matches[0].append_request_ids(matches.request_ids) return matches[0] + def find_images(self, names_or_ids): + """Find multiple images by name or id (user provided input). + + :param names_or_ids: A list of strings to use to find images. + :returns: novaclient.v2.images.Image objects for each images found + :raises exceptions.NotFound: If one or more images is not found + :raises exceptions.ClientException: If the image service returns any + unexpected images. + + NOTE: This method always makes two calls to the image service, even if + only one image is provided by ID and is returned in the first query. + """ + with self.alternate_service_type( + 'image', allowed_types=('image',)): + matches = self._list('/v2/images?id=in:%s' % ','.join( + names_or_ids), 'images') + matches.extend(self._list('/v2/images?names=in:%s' % ','.join( + names_or_ids), 'images')) + missed = (set(names_or_ids) - + set(m.name for m in matches) - + set(m.id for m in matches)) + if missed: + msg = _("Unable to find image(s): %(images)s") % { + "images": ",".join(missed)} + raise exceptions.NotFound(404, msg) + for match in matches: + match.append_request_ids(matches.request_ids) + + additional = [] + for i in matches: + if i.name not in names_or_ids and i.id not in names_or_ids: + additional.append(i) + if additional: + msg = _('Additional images found in response') + raise exceptions.ClientException(500, msg) + return matches + def list(self): """ Get a detailed list of all images. diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 2125ea99..818006b9 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -2570,6 +2570,14 @@ def _find_image(cs, image): raise exceptions.CommandError(six.text_type(e)) +def _find_images(cs, images): + """Get images by name or ID.""" + try: + return cs.glance.find_images(images) + except (exceptions.NotFound, exceptions.NoUniqueMatch) as e: + raise exceptions.CommandError(six.text_type(e)) + + def _find_flavor(cs, flavor): """Get a flavor by name, ID, or RAM size.""" try: -- cgit v1.2.1