summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAbhishek Kekane <akekane@redhat.com>2021-02-18 07:59:29 +0000
committerDan Smith <dansmith@redhat.com>2021-03-02 10:02:14 -0800
commite0a35a1150a7afe1e28b8d9b59a9e41951276baa (patch)
treec6640b52ab3b8f8871ab40be703e4cd59dbf52f8
parente8f427e1088b6de488bfa6af811d62415b073c34 (diff)
downloadpython-glanceclient-e0a35a1150a7afe1e28b8d9b59a9e41951276baa.tar.gz
Get tasks associated with image
Add support to get tasks associated with specific image. bp: messages-api Change-Id: Ia505cf6f47ca6c628e195be3ca5231d22d53040d
-rw-r--r--glanceclient/common/utils.py28
-rw-r--r--glanceclient/tests/unit/v2/base.py7
-rw-r--r--glanceclient/tests/unit/v2/test_images.py30
-rw-r--r--glanceclient/tests/unit/v2/test_shell_v2.py52
-rw-r--r--glanceclient/v2/images.py19
-rw-r--r--glanceclient/v2/shell.py18
-rw-r--r--releasenotes/notes/image-tasks-api-ee3ea043557a1dfa.yaml5
7 files changed, 159 insertions, 0 deletions
diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py
index 4084e0e..1691264 100644
--- a/glanceclient/common/utils.py
+++ b/glanceclient/common/utils.py
@@ -173,6 +173,34 @@ def pretty_choice_list(l):
return ', '.join("'%s'" % i for i in l)
+def has_version(client, version):
+ versions = client.get('/versions')[1].get('versions')
+ supported = ['SUPPORTED', 'CURRENT']
+ for version_struct in versions:
+ if version_struct['id'] == version:
+ return version_struct['status'] in supported
+ return False
+
+
+def print_dict_list(objects, fields):
+ pt = prettytable.PrettyTable([f for f in fields], caching=False)
+ pt.align = 'l'
+ for o in objects:
+ row = []
+ for field in fields:
+ field_name = field.lower().replace(' ', '_')
+ # NOTE (abhishekk) mapping field to actual name in the
+ # structure.
+ if field_name == 'task_id':
+ field_name = 'id'
+ data = o.get(field_name, '')
+ row.append(data)
+
+ pt.add_row(row)
+
+ print(encodeutils.safe_decode(pt.get_string()))
+
+
def print_list(objs, fields, formatters=None, field_settings=None):
'''Prints a list of objects.
diff --git a/glanceclient/tests/unit/v2/base.py b/glanceclient/tests/unit/v2/base.py
index d6f5cc5..694cd0f 100644
--- a/glanceclient/tests/unit/v2/base.py
+++ b/glanceclient/tests/unit/v2/base.py
@@ -38,6 +38,13 @@ class BaseController(testtools.TestCase):
return resources
+ def get_associated_image_tasks(self, *args, **kwargs):
+ resource = self.controller.get_associated_image_tasks(
+ *args, **kwargs)
+
+ self._assertRequestId(resource)
+ return resource
+
def get(self, *args, **kwargs):
resource = self.controller.get(*args, **kwargs)
diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py
index 55610d8..199d6ec 100644
--- a/glanceclient/tests/unit/v2/test_images.py
+++ b/glanceclient/tests/unit/v2/test_images.py
@@ -20,6 +20,7 @@ from unittest import mock
import ddt
+from glanceclient.common import utils as common_utils
from glanceclient import exc
from glanceclient.tests.unit.v2 import base
from glanceclient.tests import utils
@@ -674,6 +675,19 @@ data_fixtures = {
]},
),
},
+ '/v2/images/3a4560a1-e585-443e-9b39-553b46ec92d1/tasks': {
+ 'GET': (
+ {},
+ {'tasks': [
+ {
+ 'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
+ 'status': 'succeed',
+ 'message': 'Copied 44 MiB',
+ 'updated_at': '2021-03-01T18:28:26.000000'
+ }
+ ]},
+ ),
+ },
}
schema_fixtures = {
@@ -715,6 +729,22 @@ class TestController(testtools.TestCase):
self.controller = base.BaseController(self.api, self.schema_api,
images.Controller)
+ def test_image_tasks_supported(self):
+ with mock.patch.object(common_utils,
+ 'has_version') as mock_has_version:
+ mock_has_version.return_value = True
+ image_tasks = self.controller.get_associated_image_tasks(
+ '3a4560a1-e585-443e-9b39-553b46ec92d1')
+ self.assertEqual(1, len(image_tasks['tasks']))
+
+ def test_image_tasks_not_supported(self):
+ with mock.patch.object(common_utils,
+ 'has_version') as mock_has_version:
+ mock_has_version.return_value = False
+ self.assertRaises(exc.HTTPNotImplemented,
+ self.controller.get_associated_image_tasks,
+ '3a4560a1-e585-443e-9b39-553b46ec92d1')
+
def test_list_images(self):
images = self.controller.list()
self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', images[0].id)
diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py
index 3f1d77a..c2aa58a 100644
--- a/glanceclient/tests/unit/v2/test_shell_v2.py
+++ b/glanceclient/tests/unit/v2/test_shell_v2.py
@@ -113,6 +113,7 @@ class ShellV2Test(testtools.TestCase):
utils.print_list = mock.Mock()
utils.print_dict = mock.Mock()
utils.save_image = mock.Mock()
+ utils.print_dict_list = mock.Mock()
def assert_exits_with_msg(self, func, func_args, err_msg=None):
with mock.patch.object(utils, 'exit') as mocked_utils_exit:
@@ -562,6 +563,57 @@ class ShellV2Test(testtools.TestCase):
'size': 1024},
max_column_width=120)
+ def _test_do_image_tasks(self, verbose=False, supported=True):
+ args = self._make_args({'id': 'pass', 'verbose': verbose})
+ expected_columns = ["Message", "Status", "Updated at"]
+ expected_output = {
+ "tasks": [
+ {
+ "image_id": "pass",
+ "id": "task_1",
+ "user_id": "user_1",
+ "request_id": "request_id_1",
+ "message": "fake_message",
+ "status": "status",
+ }
+ ]
+ }
+
+ if verbose:
+ columns_to_prepend = ['Image Id', 'Task Id']
+ columns_to_extend = ['User Id', 'Request Id',
+ 'Result', 'Owner', 'Input', 'Expires at']
+ expected_columns = (columns_to_prepend + expected_columns +
+ columns_to_extend)
+ expected_output["tasks"][0]["Result"] = "Fake Result"
+ expected_output["tasks"][0]["Owner"] = "Fake Owner"
+ expected_output["tasks"][0]["Input"] = "Fake Input"
+ expected_output["tasks"][0]["Expires at"] = "Fake Expiry"
+
+ with mock.patch.object(self.gc.images,
+ 'get_associated_image_tasks') as mocked_tasks:
+ if supported:
+ mocked_tasks.return_value = expected_output
+ else:
+ mocked_tasks.side_effect = exc.HTTPNotImplemented
+ test_shell.do_image_tasks(self.gc, args)
+ mocked_tasks.assert_called_once_with('pass')
+ if supported:
+ utils.print_dict_list.assert_called_once_with(
+ expected_output['tasks'], expected_columns)
+
+ def test_do_image_tasks_without_verbose(self):
+ self._test_do_image_tasks()
+
+ def test_do_image_tasks_with_verbose(self):
+ self._test_do_image_tasks(verbose=True)
+
+ def test_do_image_tasks_unsupported(self):
+ with mock.patch('glanceclient.common.utils.exit') as mock_exit:
+ self._test_do_image_tasks(supported=False)
+ mock_exit.assert_called_once_with(
+ 'Server does not support image tasks API (v2.12)')
+
@mock.patch('sys.stdin', autospec=True)
def test_do_image_create_no_user_props(self, mock_stdin):
args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd',
diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py
index 341485d..b412c42 100644
--- a/glanceclient/v2/images.py
+++ b/glanceclient/v2/images.py
@@ -197,6 +197,25 @@ class Controller(object):
return self._get(image_id)
@utils.add_req_id_to_object()
+ def get_associated_image_tasks(self, image_id):
+ """Get the tasks associated with an image.
+
+ :param image_id: ID of the image
+ :raises: exc.HTTPNotImplemented if Glance is not new enough to support
+ this API (v2.12).
+ """
+ # NOTE (abhishekk): Verify that /v2i/images/%s/tasks is supported by
+ # glance
+ if utils.has_version(self.http_client, 'v2.12'):
+ url = '/v2/images/%s/tasks' % image_id
+ resp, body = self.http_client.get(url)
+ body.pop('self', None)
+ return body, resp
+ else:
+ raise exc.HTTPNotImplemented(
+ 'This operation is not supported by Glance.')
+
+ @utils.add_req_id_to_object()
def data(self, image_id, do_checksum=True, allow_md5_fallback=False):
"""Retrieve data of an image.
diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py
index 592b2da..c38d046 100644
--- a/glanceclient/v2/shell.py
+++ b/glanceclient/v2/shell.py
@@ -468,6 +468,24 @@ def do_image_show(gc, args):
utils.print_image(image, args.human_readable, int(args.max_column_width))
+@utils.arg('id', metavar='<IMAGE_ID>', help=_('ID of image to get tasks.'))
+def do_image_tasks(gc, args):
+ """Get tasks associated with image"""
+ columns = ['Message', 'Status', 'Updated at']
+ if args.verbose:
+ columns_to_prepend = ['Image Id', 'Task Id']
+ columns_to_extend = ['User Id', 'Request Id',
+ 'Result', 'Owner', 'Input', 'Expires at']
+ columns = columns_to_prepend + columns + columns_to_extend
+ try:
+ tasks = gc.images.get_associated_image_tasks(args.id)
+ utils.print_dict_list(tasks['tasks'], columns)
+ except exc.HTTPNotFound:
+ utils.exit('Image %s not found.' % args.id)
+ except exc.HTTPNotImplemented:
+ utils.exit('Server does not support image tasks API (v2.12)')
+
+
@utils.arg('--image-id', metavar='<IMAGE_ID>', required=True,
help=_('Image to display members of.'))
def do_member_list(gc, args):
diff --git a/releasenotes/notes/image-tasks-api-ee3ea043557a1dfa.yaml b/releasenotes/notes/image-tasks-api-ee3ea043557a1dfa.yaml
new file mode 100644
index 0000000..3e42451
--- /dev/null
+++ b/releasenotes/notes/image-tasks-api-ee3ea043557a1dfa.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Support for showing tasks associated with given image.
+