Merge "Get tasks associated with image"

This commit is contained in:
Zuul 2021-03-05 20:15:22 +00:00 committed by Gerrit Code Review
commit f802c71083
7 changed files with 159 additions and 0 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

@ -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',

View File

@ -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.

View File

@ -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):

View File

@ -0,0 +1,5 @@
---
features:
- |
Support for showing tasks associated with given image.