Get tasks associated with image
Add support to get tasks associated with specific image. bp: messages-api Change-Id: Ia505cf6f47ca6c628e195be3ca5231d22d53040d
This commit is contained in:
parent
e8f427e108
commit
e0a35a1150
|
@ -173,6 +173,34 @@ def pretty_choice_list(l):
|
||||||
return ', '.join("'%s'" % i for i in 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):
|
def print_list(objs, fields, formatters=None, field_settings=None):
|
||||||
'''Prints a list of objects.
|
'''Prints a list of objects.
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,13 @@ class BaseController(testtools.TestCase):
|
||||||
|
|
||||||
return resources
|
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):
|
def get(self, *args, **kwargs):
|
||||||
resource = self.controller.get(*args, **kwargs)
|
resource = self.controller.get(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ from unittest import mock
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
|
|
||||||
|
from glanceclient.common import utils as common_utils
|
||||||
from glanceclient import exc
|
from glanceclient import exc
|
||||||
from glanceclient.tests.unit.v2 import base
|
from glanceclient.tests.unit.v2 import base
|
||||||
from glanceclient.tests import utils
|
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 = {
|
schema_fixtures = {
|
||||||
|
@ -715,6 +729,22 @@ class TestController(testtools.TestCase):
|
||||||
self.controller = base.BaseController(self.api, self.schema_api,
|
self.controller = base.BaseController(self.api, self.schema_api,
|
||||||
images.Controller)
|
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):
|
def test_list_images(self):
|
||||||
images = self.controller.list()
|
images = self.controller.list()
|
||||||
self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', images[0].id)
|
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_list = mock.Mock()
|
||||||
utils.print_dict = mock.Mock()
|
utils.print_dict = mock.Mock()
|
||||||
utils.save_image = 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):
|
def assert_exits_with_msg(self, func, func_args, err_msg=None):
|
||||||
with mock.patch.object(utils, 'exit') as mocked_utils_exit:
|
with mock.patch.object(utils, 'exit') as mocked_utils_exit:
|
||||||
|
@ -562,6 +563,57 @@ class ShellV2Test(testtools.TestCase):
|
||||||
'size': 1024},
|
'size': 1024},
|
||||||
max_column_width=120)
|
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)
|
@mock.patch('sys.stdin', autospec=True)
|
||||||
def test_do_image_create_no_user_props(self, mock_stdin):
|
def test_do_image_create_no_user_props(self, mock_stdin):
|
||||||
args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd',
|
args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd',
|
||||||
|
|
|
@ -196,6 +196,25 @@ class Controller(object):
|
||||||
def get(self, image_id):
|
def get(self, image_id):
|
||||||
return self._get(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()
|
@utils.add_req_id_to_object()
|
||||||
def data(self, image_id, do_checksum=True, allow_md5_fallback=False):
|
def data(self, image_id, do_checksum=True, allow_md5_fallback=False):
|
||||||
"""Retrieve data of an image.
|
"""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.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,
|
@utils.arg('--image-id', metavar='<IMAGE_ID>', required=True,
|
||||||
help=_('Image to display members of.'))
|
help=_('Image to display members of.'))
|
||||||
def do_member_list(gc, args):
|
def do_member_list(gc, args):
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Support for showing tasks associated with given image.
|
||||||
|
|
Loading…
Reference in New Issue