Merge "Get tasks associated with image"
This commit is contained in:
commit
f802c71083
@ -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.
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
|
@ -196,6 +196,25 @@ class Controller(object):
|
||||
def get(self, image_id):
|
||||
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.
|
||||
|
@ -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):
|
||||
|
5
releasenotes/notes/image-tasks-api-ee3ea043557a1dfa.yaml
Normal file
5
releasenotes/notes/image-tasks-api-ee3ea043557a1dfa.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Support for showing tasks associated with given image.
|
||||
|
Loading…
x
Reference in New Issue
Block a user