From d940a4024446663150d20da695fd52459993dcd4 Mon Sep 17 00:00:00 2001 From: "shubham.git" Date: Tue, 29 Nov 2016 08:50:46 +0530 Subject: [PATCH] Add image command support in zunclient With commit : https://review.openstack.org/#/c/380298/ image endpoint was added. This commit adds the image endpoints in zunclient Change-Id: Ie8a986baca41493aa960a8aa6934a73776501589 Implements: blueprint add-image-endpoint --- zunclient/tests/v1/test_images.py | 163 ++++++++++++++++++++++++ zunclient/tests/v1/test_images_shell.py | 31 +++++ zunclient/v1/client.py | 2 + zunclient/v1/images.py | 96 ++++++++++++++ zunclient/v1/images_shell.py | 59 +++++++++ zunclient/v1/shell.py | 2 + 6 files changed, 353 insertions(+) create mode 100644 zunclient/tests/v1/test_images.py create mode 100644 zunclient/tests/v1/test_images_shell.py create mode 100644 zunclient/v1/images.py create mode 100644 zunclient/v1/images_shell.py diff --git a/zunclient/tests/v1/test_images.py b/zunclient/tests/v1/test_images.py new file mode 100644 index 00000000..c9169f51 --- /dev/null +++ b/zunclient/tests/v1/test_images.py @@ -0,0 +1,163 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import testtools +from testtools import matchers + +from zunclient.tests import utils +from zunclient.v1 import images + + +IMAGE1 = {'uuid': '092e2ed7-af11-4fa7-8ffa-c3ee9d5b451a', + 'image_id': 'b8ff79200466', + 'repo': 'fake-repo1', + 'tag': 'latest', + 'size': '1024', + } +IMAGE2 = {'uuid': '1996ba70-b074-454b-a8fc-0895ae26c7c6', + 'image_id': '21c16b6787c6', + 'repo': 'fake-repo2', + 'tag': 'latest', + 'size': '1024', + } + +fake_responses = { + '/v1/images/': + { + 'GET': ( + {}, + {'images': [IMAGE1, IMAGE2]}, + ), + }, + '/v1/images/?limit=2': + { + 'GET': ( + {}, + {'images': [IMAGE1, IMAGE2]}, + ), + }, + '/v1/images/?marker=%s' % IMAGE2['image_id']: + { + 'GET': ( + {}, + {'images': [IMAGE1, IMAGE2]}, + ), + }, + '/v1/images/?limit=2&marker=%s' % IMAGE2['image_id']: + { + 'GET': ( + {}, + {'images': [IMAGE2, IMAGE1]}, + ), + }, + '/v1/images/?sort_dir=asc': + { + 'GET': ( + {}, + {'images': [IMAGE1, IMAGE2]}, + ), + }, + '/v1/images/?sort_key=image_id': + { + 'GET': ( + {}, + {'images': [IMAGE1, IMAGE2]}, + ), + }, + '/v1/images/?sort_key=image_id&sort_dir=desc': + { + 'GET': ( + {}, + {'images': [IMAGE2, IMAGE1]}, + ), + }, +} + + +class ImageManagerTest(testtools.TestCase): + + def setUp(self): + super(ImageManagerTest, self).setUp() + self.api = utils.FakeAPI(fake_responses) + self.mgr = images.ImageManager(self.api) + + def test_image_list(self): + images = self.mgr.list() + expect = [ + ('GET', '/v1/images/', {}, None), + ] + self.assertEqual(expect, self.api.calls) + self.assertThat(images, matchers.HasLength(2)) + + def _test_image_list_with_filters( + self, limit=None, marker=None, + sort_key=None, sort_dir=None, + detail=False, expect=[]): + images_filter = self.mgr.list(limit=limit, marker=marker, + sort_key=sort_key, + sort_dir=sort_dir, + detail=detail) + self.assertEqual(expect, self.api.calls) + self.assertThat(images_filter, matchers.HasLength(2)) + + def test_image_list_with_limit(self): + expect = [ + ('GET', '/v1/images/?limit=2', {}, None), + ] + self._test_image_list_with_filters( + limit=2, + expect=expect) + + def test_image_list_with_marker(self): + expect = [ + ('GET', '/v1/images/?marker=%s' % IMAGE2['image_id'], + {}, None), + ] + self._test_image_list_with_filters( + marker=IMAGE2['image_id'], + expect=expect) + + def test_image_list_with_marker_limit(self): + expect = [ + ('GET', '/v1/images/?limit=2&marker=%s' % IMAGE2['image_id'], + {}, None), + ] + self._test_image_list_with_filters( + limit=2, marker=IMAGE2['image_id'], + expect=expect) + + def test_image_list_with_sort_dir(self): + expect = [ + ('GET', '/v1/images/?sort_dir=asc', + {}, None), + ] + self._test_image_list_with_filters( + sort_dir='asc', + expect=expect) + + def test_image_list_with_sort_key(self): + expect = [ + ('GET', '/v1/images/?sort_key=image_id', + {}, None), + ] + self._test_image_list_with_filters( + sort_key='image_id', + expect=expect) + + def test_image_list_with_sort_key_dir(self): + expect = [ + ('GET', '/v1/images/?sort_key=image_id&sort_dir=desc', + {}, None), + ] + self._test_image_list_with_filters( + sort_key='image_id', sort_dir='desc', + expect=expect) diff --git a/zunclient/tests/v1/test_images_shell.py b/zunclient/tests/v1/test_images_shell.py new file mode 100644 index 00000000..f86d9a09 --- /dev/null +++ b/zunclient/tests/v1/test_images_shell.py @@ -0,0 +1,31 @@ +# Copyright 2015 NEC Corporation. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock + +from zunclient.tests.v1 import shell_test_base + + +class ShellTest(shell_test_base.TestCommandLineArgument): + + @mock.patch('zunclient.v1.images.ImageManager.list') + def test_zun_image_list_success(self, mock_list): + self._test_arg_success('image-list') + self.assertTrue(mock_list.called) + + @mock.patch('zunclient.v1.images.ImageManager.list') + def test_zun_image_list_failure(self, mock_list): + self._test_arg_failure('image-list --wrong', + self._unrecognized_arg_error) + self.assertFalse(mock_list.called) diff --git a/zunclient/v1/client.py b/zunclient/v1/client.py index 584a04f6..9628336f 100644 --- a/zunclient/v1/client.py +++ b/zunclient/v1/client.py @@ -18,6 +18,7 @@ from keystoneauth1 import session as ksa_session from zunclient.common import httpclient from zunclient.v1 import containers +from zunclient.v1 import images from zunclient.v1 import services @@ -108,4 +109,5 @@ class Client(object): session=session, **client_kwargs) self.containers = containers.ContainerManager(self.http_client) + self.images = images.ImageManager(self.http_client) self.services = services.ServiceManager(self.http_client) diff --git a/zunclient/v1/images.py b/zunclient/v1/images.py new file mode 100644 index 00000000..4f92613d --- /dev/null +++ b/zunclient/v1/images.py @@ -0,0 +1,96 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from zunclient.common import base +from zunclient.common import utils +from zunclient import exceptions + + +PULL_ATTRIBUTES = ['repo'] + + +class Image(base.Resource): + def __repr__(self): + return "" % self._info + + +class ImageManager(base.Manager): + resource_class = Image + + @staticmethod + def _path(id=None): + + if id: + return '/v1/images/%s' % id + else: + return '/v1/images/' + + def list(self, marker=None, limit=None, sort_key=None, + sort_dir=None, detail=False): + """Retrieve a list of images. + + :param marker: Optional, the UUID of an image, eg the last + image from a previous result set. Return + the next result set. + :param limit: The maximum number of results to return per + request, if: + + 1) limit > 0, the maximum number of images to return. + 2) limit == 0, return the entire list of images. + 3) limit param is NOT specified (None), the number of items + returned respect the maximum imposed by the Zun api + + :param sort_key: Optional, field used for sorting. + + :param sort_dir: Optional, direction of sorting, either 'asc' (the + default) or 'desc'. + + :param detail: Optional, boolean whether to return detailed information + about images. + + :returns: A list of images. + + """ + if limit is not None: + limit = int(limit) + + filters = utils.common_filters(marker, limit, sort_key, sort_dir) + + path = '' + if detail: + path += 'detail' + if filters: + path += '?' + '&'.join(filters) + + if limit is None: + return self._list(self._path(path), + "images") + else: + return self._list_pagination(self._path(path), + "images", + limit=limit) + + def get(self, id): + try: + return self._list(self._path(id))[0] + except IndexError: + return None + + def create(self, **kwargs): + new = {} + for (key, value) in kwargs.items(): + if key in PULL_ATTRIBUTES: + new[key] = value + else: + raise exceptions.InvalidAttribute( + "Key must be in %s" % ','.join(PULL_ATTRIBUTES)) + return self._create(self._path(), new) diff --git a/zunclient/v1/images_shell.py b/zunclient/v1/images_shell.py new file mode 100644 index 00000000..ecb3fcaa --- /dev/null +++ b/zunclient/v1/images_shell.py @@ -0,0 +1,59 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from zunclient.common import cliutils as utils +from zunclient.common import utils as zun_utils + + +def _show_image(image): + utils.print_dict(image._info) + + +@utils.arg('-r', '--repo', + required=True, + metavar='', + help='image repo') +def do_pull(cs, args): + """Pull an image.""" + opts = {} + opts['repo'] = args.repo + _show_image(cs.images.create(**opts)) + + +@utils.arg('--marker', + metavar='', + default=None, + help='The last image UUID of the previous page; ' + 'displays list of images after "marker".') +@utils.arg('--limit', + metavar='', + type=int, + help='Maximum number of images to return') +@utils.arg('--sort-key', + metavar='', + help='Column to sort results by') +@utils.arg('--sort-dir', + metavar='', + choices=['desc', 'asc'], + help='Direction to sort. "asc" or "desc".') +def do_image_list(cs, args): + """Print a list of available images.""" + opts = {} + opts['marker'] = args.marker + opts['limit'] = args.limit + opts['sort_key'] = args.sort_key + opts['sort_dir'] = args.sort_dir + images = cs.images.list(**opts) + columns = ('uuid', 'image_id', 'repo', 'tag', 'size') + utils.print_list(images, columns, + {'versions': zun_utils.print_list_field('versions')}, + sortby_index=None) diff --git a/zunclient/v1/shell.py b/zunclient/v1/shell.py index c1c707ad..e43694eb 100644 --- a/zunclient/v1/shell.py +++ b/zunclient/v1/shell.py @@ -14,9 +14,11 @@ # limitations under the License. from zunclient.v1 import containers_shell +from zunclient.v1 import images_shell from zunclient.v1 import services_shell COMMAND_MODULES = [ containers_shell, + images_shell, services_shell, ]