Merge "Complete switch from glanceclient to SDK for image service"

This commit is contained in:
Zuul 2020-03-25 15:19:21 +00:00 committed by Gerrit Code Review
commit 74616cd235
6 changed files with 155 additions and 229 deletions

View File

@ -26,64 +26,18 @@ DEFAULT_API_VERSION = '2'
API_VERSION_OPTION = 'os_image_api_version' API_VERSION_OPTION = 'os_image_api_version'
API_NAME = "image" API_NAME = "image"
API_VERSIONS = { API_VERSIONS = {
"1": "glanceclient.v1.client.Client", "1": "openstack.connection.Connection",
"2": "openstack.connection.Connection", "2": "openstack.connection.Connection",
} }
IMAGE_API_TYPE = 'image'
IMAGE_API_VERSIONS = {
'1': 'openstackclient.api.image_v1.APIv1',
'2': 'openstackclient.api.image_v2.APIv2',
}
def make_client(instance): def make_client(instance):
if instance._api_version[API_NAME] != '1': LOG.debug(
LOG.debug( 'Image client initialized using OpenStack SDK: %s',
'Image client initialized using OpenStack SDK: %s', instance.sdk_connection.image,
instance.sdk_connection.image, )
) return instance.sdk_connection.image
return instance.sdk_connection.image
else:
"""Returns an image service client"""
image_client = utils.get_client_class(
API_NAME,
instance._api_version[API_NAME],
API_VERSIONS)
LOG.debug('Instantiating image client: %s', image_client)
endpoint = instance.get_endpoint_for_service_type(
API_NAME,
region_name=instance.region_name,
interface=instance.interface,
)
client = image_client(
endpoint,
token=instance.auth.get_token(instance.session),
cacert=instance.cacert,
insecure=not instance.verify,
)
# Create the low-level API
image_api = utils.get_client_class(
API_NAME,
instance._api_version[API_NAME],
IMAGE_API_VERSIONS)
LOG.debug('Instantiating image api: %s', image_api)
client.api = image_api(
session=instance.session,
endpoint=instance.get_endpoint_for_service_type(
IMAGE_API_TYPE,
region_name=instance.region_name,
interface=instance.interface,
)
)
return client
def build_option_parser(parser): def build_option_parser(parser):

View File

@ -22,13 +22,13 @@ import os
import sys import sys
from cliff import columns as cliff_columns from cliff import columns as cliff_columns
from glanceclient.common import utils as gc_utils
from osc_lib.api import utils as api_utils from osc_lib.api import utils as api_utils
from osc_lib.cli import format_columns from osc_lib.cli import format_columns
from osc_lib.cli import parseractions from osc_lib.cli import parseractions
from osc_lib.command import command from osc_lib.command import command
from osc_lib import utils from osc_lib import utils
from openstackclient.common import sdk_utils
from openstackclient.i18n import _ from openstackclient.i18n import _
if os.name == "nt": if os.name == "nt":
@ -47,6 +47,36 @@ DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx",
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
def _get_columns(item):
# Trick sdk_utils to return URI attribute
column_map = {
'is_protected': 'protected',
'owner_id': 'owner'
}
hidden_columns = ['location', 'checksum',
'copy_from', 'created_at', 'status', 'updated_at']
return sdk_utils.get_osc_show_columns_for_sdk_resource(
item.to_dict(), column_map, hidden_columns)
_formatters = {
}
class HumanReadableSizeColumn(cliff_columns.FormattableColumn):
def human_readable(self):
"""Return a formatted visibility string
:rtype:
A string formatted to public/private
"""
if self._value:
return utils.format_size(self._value)
else:
return ''
class VisibilityColumn(cliff_columns.FormattableColumn): class VisibilityColumn(cliff_columns.FormattableColumn):
def human_readable(self): def human_readable(self):
"""Return a formatted visibility string """Return a formatted visibility string
@ -210,7 +240,7 @@ class CreateImage(command.ShowOne):
# Special case project option back to API attribute name 'owner' # Special case project option back to API attribute name 'owner'
val = getattr(parsed_args, 'project', None) val = getattr(parsed_args, 'project', None)
if val: if val:
kwargs['owner'] = val kwargs['owner_id'] = val
# Handle exclusive booleans with care # Handle exclusive booleans with care
# Avoid including attributes in kwargs if an option is not # Avoid including attributes in kwargs if an option is not
@ -219,9 +249,9 @@ class CreateImage(command.ShowOne):
# to do nothing when no options are present as opposed to always # to do nothing when no options are present as opposed to always
# setting a default. # setting a default.
if parsed_args.protected: if parsed_args.protected:
kwargs['protected'] = True kwargs['is_protected'] = True
if parsed_args.unprotected: if parsed_args.unprotected:
kwargs['protected'] = False kwargs['is_protected'] = False
if parsed_args.public: if parsed_args.public:
kwargs['is_public'] = True kwargs['is_public'] = True
if parsed_args.private: if parsed_args.private:
@ -250,27 +280,35 @@ class CreateImage(command.ShowOne):
kwargs["data"] = io.open(parsed_args.file, "rb") kwargs["data"] = io.open(parsed_args.file, "rb")
else: else:
# Read file from stdin # Read file from stdin
if sys.stdin.isatty() is not True: if not sys.stdin.isatty():
if msvcrt: if msvcrt:
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
# Send an open file handle to glanceclient so it will if hasattr(sys.stdin, 'buffer'):
# do a chunked transfer kwargs['data'] = sys.stdin.buffer
kwargs["data"] = sys.stdin else:
kwargs["data"] = sys.stdin
if not parsed_args.volume: if not parsed_args.volume:
# Wrap the call to catch exceptions in order to close files # Wrap the call to catch exceptions in order to close files
try: try:
image = image_client.images.create(**kwargs) image = image_client.create_image(**kwargs)
finally: finally:
# Clean up open files - make sure data isn't a string # Clean up open files - make sure data isn't a string
if ('data' in kwargs and hasattr(kwargs['data'], 'close') and if ('data' in kwargs and hasattr(kwargs['data'], 'close') and
kwargs['data'] != sys.stdin): kwargs['data'] != sys.stdin):
kwargs['data'].close() kwargs['data'].close()
if image:
display_columns, columns = _get_columns(image)
_formatters['properties'] = format_columns.DictColumn
data = utils.get_item_properties(image, columns,
formatters=_formatters)
return (display_columns, data)
elif info:
info.update(image._info) info.update(image._info)
info['properties'] = format_columns.DictColumn( info['properties'] = format_columns.DictColumn(
info.get('properties', {})) info.get('properties', {}))
return zip(*sorted(info.items())) return zip(*sorted(info.items()))
class DeleteImage(command.Command): class DeleteImage(command.Command):
@ -289,11 +327,8 @@ class DeleteImage(command.Command):
def take_action(self, parsed_args): def take_action(self, parsed_args):
image_client = self.app.client_manager.image image_client = self.app.client_manager.image
for image in parsed_args.images: for image in parsed_args.images:
image_obj = utils.find_resource( image_obj = image_client.find_image(image)
image_client.images, image_client.delete_image(image_obj.id)
image,
)
image_client.images.delete(image_obj.id)
class ListImage(command.Lister): class ListImage(command.Lister):
@ -359,15 +394,9 @@ class ListImage(command.Lister):
kwargs = {} kwargs = {}
if parsed_args.public: if parsed_args.public:
kwargs['public'] = True kwargs['is_public'] = True
if parsed_args.private: if parsed_args.private:
kwargs['private'] = True kwargs['is_private'] = True
# Note: We specifically need to do that below to get the 'status'
# column.
#
# Always set kwargs['detailed'] to True, and then filter the columns
# according to whether the --long option is specified or not.
kwargs['detailed'] = True
if parsed_args.long: if parsed_args.long:
columns = ( columns = (
@ -379,8 +408,8 @@ class ListImage(command.Lister):
'Checksum', 'Checksum',
'Status', 'Status',
'is_public', 'is_public',
'protected', 'is_protected',
'owner', 'owner_id',
'properties', 'properties',
) )
column_headers = ( column_headers = (
@ -401,16 +430,7 @@ class ListImage(command.Lister):
column_headers = columns column_headers = columns
# List of image data received # List of image data received
data = [] data = list(image_client.images(**kwargs))
# No pages received yet, so start the page marker at None.
marker = None
while True:
page = image_client.api.image_list(marker=marker, **kwargs)
if not page:
break
data.extend(page)
# Set the marker to the id of the last item we received
marker = page[-1]['id']
if parsed_args.property: if parsed_args.property:
# NOTE(dtroyer): coerce to a list to subscript it in py3 # NOTE(dtroyer): coerce to a list to subscript it in py3
@ -426,7 +446,7 @@ class ListImage(command.Lister):
return ( return (
column_headers, column_headers,
(utils.get_dict_properties( (utils.get_item_properties(
s, s,
columns, columns,
formatters={ formatters={
@ -456,13 +476,9 @@ class SaveImage(command.Command):
def take_action(self, parsed_args): def take_action(self, parsed_args):
image_client = self.app.client_manager.image image_client = self.app.client_manager.image
image = utils.find_resource( image = image_client.find_image(parsed_args.image)
image_client.images,
parsed_args.image,
)
data = image_client.images.data(image)
gc_utils.save_image(data, parsed_args.file) image_client.download_image(image.id, output=parsed_args.file)
class SetImage(command.Command): class SetImage(command.Command):
@ -621,22 +637,17 @@ class SetImage(command.Command):
# to do nothing when no options are present as opposed to always # to do nothing when no options are present as opposed to always
# setting a default. # setting a default.
if parsed_args.protected: if parsed_args.protected:
kwargs['protected'] = True kwargs['is_protected'] = True
if parsed_args.unprotected: if parsed_args.unprotected:
kwargs['protected'] = False kwargs['is_protected'] = False
if parsed_args.public: if parsed_args.public:
kwargs['is_public'] = True kwargs['is_public'] = True
if parsed_args.private: if parsed_args.private:
kwargs['is_public'] = False kwargs['is_public'] = False
if parsed_args.force:
kwargs['force'] = True
# Wrap the call to catch exceptions in order to close files # Wrap the call to catch exceptions in order to close files
try: try:
image = utils.find_resource( image = image_client.find_image(parsed_args.image)
image_client.images,
parsed_args.image,
)
if not parsed_args.location and not parsed_args.copy_from: if not parsed_args.location and not parsed_args.copy_from:
if parsed_args.volume: if parsed_args.volume:
@ -666,9 +677,10 @@ class SetImage(command.Command):
if parsed_args.stdin: if parsed_args.stdin:
if msvcrt: if msvcrt:
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
# Send an open file handle to glanceclient so it if hasattr(sys.stdin, 'buffer'):
# will do a chunked transfer kwargs['data'] = sys.stdin.buffer
kwargs["data"] = sys.stdin else:
kwargs["data"] = sys.stdin
else: else:
LOG.warning(_('Use --stdin to enable read image ' LOG.warning(_('Use --stdin to enable read image '
'data from standard input')) 'data from standard input'))
@ -677,7 +689,7 @@ class SetImage(command.Command):
image.properties.update(kwargs['properties']) image.properties.update(kwargs['properties'])
kwargs['properties'] = image.properties kwargs['properties'] = image.properties
image = image_client.images.update(image.id, **kwargs) image = image_client.update_image(image.id, **kwargs)
finally: finally:
# Clean up open files - make sure data isn't a string # Clean up open files - make sure data isn't a string
if ('data' in kwargs and hasattr(kwargs['data'], 'close') and if ('data' in kwargs and hasattr(kwargs['data'], 'close') and
@ -705,16 +717,12 @@ class ShowImage(command.ShowOne):
def take_action(self, parsed_args): def take_action(self, parsed_args):
image_client = self.app.client_manager.image image_client = self.app.client_manager.image
image = utils.find_resource( image = image_client.find_image(parsed_args.image)
image_client.images,
parsed_args.image,
)
info = {}
info.update(image._info)
if parsed_args.human_readable: if parsed_args.human_readable:
if 'size' in info: _formatters['size'] = HumanReadableSizeColumn
info['size'] = utils.format_size(info['size']) display_columns, columns = _get_columns(image)
info['properties'] = format_columns.DictColumn( _formatters['properties'] = format_columns.DictColumn
info.get('properties', {})) data = utils.get_item_properties(image, columns,
return zip(*sorted(info.items())) formatters=_formatters)
return (display_columns, data)

View File

@ -13,10 +13,11 @@
# under the License. # under the License.
# #
import copy
from unittest import mock from unittest import mock
import uuid import uuid
from openstack.image.v1 import image
from openstackclient.tests.unit import fakes from openstackclient.tests.unit import fakes
from openstackclient.tests.unit import utils from openstackclient.tests.unit import utils
from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes
@ -111,13 +112,10 @@ class FakeImage(object):
'Alpha': 'a', 'Alpha': 'a',
'Beta': 'b', 'Beta': 'b',
'Gamma': 'g'}, 'Gamma': 'g'},
'status': 'status' + uuid.uuid4().hex
} }
# Overwrite default attributes if there are some attributes set # Overwrite default attributes if there are some attributes set
image_info.update(attrs) image_info.update(attrs)
image = fakes.FakeResource( return image.Image(**image_info)
info=copy.deepcopy(image_info),
loaded=True)
return image

View File

@ -17,7 +17,6 @@ import copy
from unittest import mock from unittest import mock
from osc_lib.cli import format_columns from osc_lib.cli import format_columns
from osc_lib import exceptions
from openstackclient.image.v1 import image from openstackclient.image.v1 import image
from openstackclient.tests.unit import fakes from openstackclient.tests.unit import fakes
@ -29,9 +28,8 @@ class TestImage(image_fakes.TestImagev1):
def setUp(self): def setUp(self):
super(TestImage, self).setUp() super(TestImage, self).setUp()
# Get a shortcut to the ServerManager Mock self.app.client_manager.image = mock.Mock()
self.images_mock = self.app.client_manager.image.images self.client = self.app.client_manager.image
self.images_mock.reset_mock()
class TestImageCreate(TestImage): class TestImageCreate(TestImage):
@ -48,6 +46,7 @@ class TestImageCreate(TestImage):
'owner', 'owner',
'properties', 'properties',
'protected', 'protected',
'size'
) )
data = ( data = (
new_image.container_format, new_image.container_format,
@ -57,28 +56,24 @@ class TestImageCreate(TestImage):
new_image.min_disk, new_image.min_disk,
new_image.min_ram, new_image.min_ram,
new_image.name, new_image.name,
new_image.owner, new_image.owner_id,
format_columns.DictColumn(new_image.properties), format_columns.DictColumn(new_image.properties),
new_image.protected, new_image.is_protected,
new_image.size
) )
def setUp(self): def setUp(self):
super(TestImageCreate, self).setUp() super(TestImageCreate, self).setUp()
self.images_mock.create.return_value = self.new_image self.client.create_image = mock.Mock(return_value=self.new_image)
# This is the return value for utils.find_resource() self.client.find_image = mock.Mock(return_value=self.new_image)
self.images_mock.get.return_value = self.new_image self.client.update_image = mock.Mock(return_image=self.new_image)
self.images_mock.update.return_value = self.new_image
# Get the command object to test # Get the command object to test
self.cmd = image.CreateImage(self.app, None) self.cmd = image.CreateImage(self.app, None)
def test_image_reserve_no_options(self): @mock.patch('sys.stdin', side_effect=[None])
mock_exception = { def test_image_reserve_no_options(self, raw_input):
'find.side_effect': exceptions.CommandError('x'),
'get.side_effect': exceptions.CommandError('x'),
}
self.images_mock.configure_mock(**mock_exception)
arglist = [ arglist = [
self.new_image.name, self.new_image.name,
] ]
@ -95,25 +90,20 @@ class TestImageCreate(TestImage):
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
# ImageManager.create(name=, **) # ImageManager.create(name=, **)
self.images_mock.create.assert_called_with( self.client.create_image.assert_called_with(
name=self.new_image.name, name=self.new_image.name,
container_format=image.DEFAULT_CONTAINER_FORMAT, container_format=image.DEFAULT_CONTAINER_FORMAT,
disk_format=image.DEFAULT_DISK_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT
data=mock.ANY,
) )
# Verify update() was not called, if it was show the args # Verify update() was not called, if it was show the args
self.assertEqual(self.images_mock.update.call_args_list, []) self.assertEqual(self.client.update_image.call_args_list, [])
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertItemEqual(self.data, data) self.assertItemEqual(self.data, data)
def test_image_reserve_options(self): @mock.patch('sys.stdin', side_effect=[None])
mock_exception = { def test_image_reserve_options(self, raw_input):
'find.side_effect': exceptions.CommandError('x'),
'get.side_effect': exceptions.CommandError('x'),
}
self.images_mock.configure_mock(**mock_exception)
arglist = [ arglist = [
'--container-format', 'ovf', '--container-format', 'ovf',
'--disk-format', 'ami', '--disk-format', 'ami',
@ -144,20 +134,19 @@ class TestImageCreate(TestImage):
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
# ImageManager.create(name=, **) # ImageManager.create(name=, **)
self.images_mock.create.assert_called_with( self.client.create_image.assert_called_with(
name=self.new_image.name, name=self.new_image.name,
container_format='ovf', container_format='ovf',
disk_format='ami', disk_format='ami',
min_disk=10, min_disk=10,
min_ram=4, min_ram=4,
protected=True, is_protected=True,
is_public=False, is_public=False,
owner='q', owner_id='q',
data=mock.ANY,
) )
# Verify update() was not called, if it was show the args # Verify update() was not called, if it was show the args
self.assertEqual(self.images_mock.update.call_args_list, []) self.assertEqual(self.client.update_image.call_args_list, [])
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertItemEqual(self.data, data) self.assertItemEqual(self.data, data)
@ -167,11 +156,6 @@ class TestImageCreate(TestImage):
mock_file = mock.Mock(name='File') mock_file = mock.Mock(name='File')
mock_open.return_value = mock_file mock_open.return_value = mock_file
mock_open.read.return_value = self.data mock_open.read.return_value = self.data
mock_exception = {
'find.side_effect': exceptions.CommandError('x'),
'get.side_effect': exceptions.CommandError('x'),
}
self.images_mock.configure_mock(**mock_exception)
arglist = [ arglist = [
'--file', 'filer', '--file', 'filer',
@ -203,15 +187,12 @@ class TestImageCreate(TestImage):
# Ensure the input file is closed # Ensure the input file is closed
mock_file.close.assert_called_with() mock_file.close.assert_called_with()
# ImageManager.get(name) not to be called since update action exists
self.images_mock.get.assert_not_called()
# ImageManager.create(name=, **) # ImageManager.create(name=, **)
self.images_mock.create.assert_called_with( self.client.create_image.assert_called_with(
name=self.new_image.name, name=self.new_image.name,
container_format=image.DEFAULT_CONTAINER_FORMAT, container_format=image.DEFAULT_CONTAINER_FORMAT,
disk_format=image.DEFAULT_DISK_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT,
protected=False, is_protected=False,
is_public=True, is_public=True,
properties={ properties={
'Alpha': '1', 'Alpha': '1',
@ -221,7 +202,7 @@ class TestImageCreate(TestImage):
) )
# Verify update() was not called, if it was show the args # Verify update() was not called, if it was show the args
self.assertEqual(self.images_mock.update.call_args_list, []) self.assertEqual(self.client.update_image.call_args_list, [])
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertItemEqual(self.data, data) self.assertItemEqual(self.data, data)
@ -235,8 +216,8 @@ class TestImageDelete(TestImage):
super(TestImageDelete, self).setUp() super(TestImageDelete, self).setUp()
# This is the return value for utils.find_resource() # This is the return value for utils.find_resource()
self.images_mock.get.return_value = self._image self.client.find_image = mock.Mock(return_value=self._image)
self.images_mock.delete.return_value = None self.client.delete_image = mock.Mock(return_value=None)
# Get the command object to test # Get the command object to test
self.cmd = image.DeleteImage(self.app, None) self.cmd = image.DeleteImage(self.app, None)
@ -252,7 +233,7 @@ class TestImageDelete(TestImage):
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
self.images_mock.delete.assert_called_with(self._image.id) self.client.delete_image.assert_called_with(self._image.id)
self.assertIsNone(result) self.assertIsNone(result)
@ -269,7 +250,7 @@ class TestImageList(TestImage):
( (
_image.id, _image.id,
_image.name, _image.name,
'', _image.status
), ),
) )
@ -277,13 +258,13 @@ class TestImageList(TestImage):
info = { info = {
'id': _image.id, 'id': _image.id,
'name': _image.name, 'name': _image.name,
'owner': _image.owner, 'owner': _image.owner_id,
'container_format': _image.container_format, 'container_format': _image.container_format,
'disk_format': _image.disk_format, 'disk_format': _image.disk_format,
'min_disk': _image.min_disk, 'min_disk': _image.min_disk,
'min_ram': _image.min_ram, 'min_ram': _image.min_ram,
'is_public': _image.is_public, 'is_public': _image.is_public,
'protected': _image.protected, 'protected': _image.is_protected,
'properties': _image.properties, 'properties': _image.properties,
} }
image_info = copy.deepcopy(info) image_info = copy.deepcopy(info)
@ -291,11 +272,10 @@ class TestImageList(TestImage):
def setUp(self): def setUp(self):
super(TestImageList, self).setUp() super(TestImageList, self).setUp()
self.api_mock = mock.Mock() self.client.images = mock.Mock()
self.api_mock.image_list.side_effect = [ self.client.images.side_effect = [
[self.image_info], [], [self._image], [],
] ]
self.app.client_manager.image.api = self.api_mock
# Get the command object to test # Get the command object to test
self.cmd = image.ListImage(self.app, None) self.cmd = image.ListImage(self.app, None)
@ -313,10 +293,7 @@ class TestImageList(TestImage):
# returns a tuple containing the column names and an iterable # returns a tuple containing the column names and an iterable
# containing the data to be listed. # containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with( self.client.images.assert_called_with()
detailed=True,
marker=self._image.id,
)
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data)) self.assertEqual(self.datalist, tuple(data))
@ -336,10 +313,8 @@ class TestImageList(TestImage):
# returns a tuple containing the column names and an iterable # returns a tuple containing the column names and an iterable
# containing the data to be listed. # containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with( self.client.images.assert_called_with(
detailed=True, is_public=True,
public=True,
marker=self._image.id,
) )
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
@ -360,10 +335,8 @@ class TestImageList(TestImage):
# returns a tuple containing the column names and an iterable # returns a tuple containing the column names and an iterable
# containing the data to be listed. # containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with( self.client.images.assert_called_with(
detailed=True, is_private=True,
private=True,
marker=self._image.id,
) )
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
@ -382,10 +355,7 @@ class TestImageList(TestImage):
# returns a tuple containing the column names and an iterable # returns a tuple containing the column names and an iterable
# containing the data to be listed. # containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with( self.client.images.assert_called_with()
detailed=True,
marker=self._image.id,
)
collist = ( collist = (
'ID', 'ID',
@ -405,14 +375,14 @@ class TestImageList(TestImage):
datalist = (( datalist = ((
self._image.id, self._image.id,
self._image.name, self._image.name,
'', self._image.disk_format,
'', self._image.container_format,
'', self._image.size,
'', self._image.checksum,
'', self._image.status,
image.VisibilityColumn(True), image.VisibilityColumn(self._image.is_public),
False, self._image.is_protected,
self._image.owner, self._image.owner_id,
format_columns.DictColumn( format_columns.DictColumn(
{'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}), {'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}),
), ) ), )
@ -436,12 +406,9 @@ class TestImageList(TestImage):
# returns a tuple containing the column names and an iterable # returns a tuple containing the column names and an iterable
# containing the data to be listed. # containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with( self.client.images.assert_called_with()
detailed=True,
marker=self._image.id,
)
sf_mock.assert_called_with( sf_mock.assert_called_with(
[self.image_info], [self._image],
attr='a', attr='a',
value='1', value='1',
property_field='properties', property_field='properties',
@ -453,7 +420,7 @@ class TestImageList(TestImage):
@mock.patch('osc_lib.utils.sort_items') @mock.patch('osc_lib.utils.sort_items')
def test_image_list_sort_option(self, si_mock): def test_image_list_sort_option(self, si_mock):
si_mock.side_effect = [ si_mock.side_effect = [
[self.image_info], [], [self._image], [],
] ]
arglist = ['--sort', 'name:asc'] arglist = ['--sort', 'name:asc']
@ -464,12 +431,9 @@ class TestImageList(TestImage):
# returns a tuple containing the column names and an iterable # returns a tuple containing the column names and an iterable
# containing the data to be listed. # containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with( self.client.images.assert_called_with()
detailed=True,
marker=self._image.id,
)
si_mock.assert_called_with( si_mock.assert_called_with(
[self.image_info], [self._image],
'name:asc' 'name:asc'
) )
@ -485,8 +449,8 @@ class TestImageSet(TestImage):
super(TestImageSet, self).setUp() super(TestImageSet, self).setUp()
# This is the return value for utils.find_resource() # This is the return value for utils.find_resource()
self.images_mock.get.return_value = self._image self.client.find_image = mock.Mock(return_value=self._image)
self.images_mock.update.return_value = self._image self.client.update_image = mock.Mock(return_value=self._image)
# Get the command object to test # Get the command object to test
self.cmd = image.SetImage(self.app, None) self.cmd = image.SetImage(self.app, None)
@ -502,8 +466,7 @@ class TestImageSet(TestImage):
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
self.images_mock.update.assert_called_with(self._image.id, self.client.update_image.assert_called_with(self._image.id, **{})
**{})
self.assertIsNone(result) self.assertIsNone(result)
def test_image_set_options(self): def test_image_set_options(self):
@ -541,7 +504,7 @@ class TestImageSet(TestImage):
'size': 35165824 'size': 35165824
} }
# ImageManager.update(image, **kwargs) # ImageManager.update(image, **kwargs)
self.images_mock.update.assert_called_with( self.client.update_image.assert_called_with(
self._image.id, self._image.id,
**kwargs **kwargs
) )
@ -565,11 +528,11 @@ class TestImageSet(TestImage):
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
kwargs = { kwargs = {
'protected': True, 'is_protected': True,
'is_public': False, 'is_public': False,
} }
# ImageManager.update(image, **kwargs) # ImageManager.update(image, **kwargs)
self.images_mock.update.assert_called_with( self.client.update_image.assert_called_with(
self._image.id, self._image.id,
**kwargs **kwargs
) )
@ -593,11 +556,11 @@ class TestImageSet(TestImage):
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
kwargs = { kwargs = {
'protected': False, 'is_protected': False,
'is_public': True, 'is_public': True,
} }
# ImageManager.update(image, **kwargs) # ImageManager.update(image, **kwargs)
self.images_mock.update.assert_called_with( self.client.update_image.assert_called_with(
self._image.id, self._image.id,
**kwargs **kwargs
) )
@ -625,7 +588,7 @@ class TestImageSet(TestImage):
}, },
} }
# ImageManager.update(image, **kwargs) # ImageManager.update(image, **kwargs)
self.images_mock.update.assert_called_with( self.client.update_image.assert_called_with(
self._image.id, self._image.id,
**kwargs **kwargs
) )
@ -683,7 +646,7 @@ class TestImageSet(TestImage):
'', '',
) )
# ImageManager.update(image_id, remove_props=, **) # ImageManager.update(image_id, remove_props=, **)
self.images_mock.update.assert_called_with( self.client.update_image.assert_called_with(
self._image.id, self._image.id,
name='updated_image', name='updated_image',
volume='volly', volume='volly',
@ -710,7 +673,7 @@ class TestImageSet(TestImage):
'min_ram': 0, 'min_ram': 0,
} }
# ImageManager.update(image, **kwargs) # ImageManager.update(image, **kwargs)
self.images_mock.update.assert_called_with( self.client.update_image.assert_called_with(
self._image.id, self._image.id,
**kwargs **kwargs
) )
@ -742,16 +705,16 @@ class TestImageShow(TestImage):
_image.min_disk, _image.min_disk,
_image.min_ram, _image.min_ram,
_image.name, _image.name,
_image.owner, _image.owner_id,
format_columns.DictColumn(_image.properties), format_columns.DictColumn(_image.properties),
_image.protected, _image.is_protected,
_image.size, _image.size,
) )
def setUp(self): def setUp(self):
super(TestImageShow, self).setUp() super(TestImageShow, self).setUp()
self.images_mock.get.return_value = self._image self.client.find_image = mock.Mock(return_value=self._image)
# Get the command object to test # Get the command object to test
self.cmd = image.ShowImage(self.app, None) self.cmd = image.ShowImage(self.app, None)
@ -769,7 +732,7 @@ class TestImageShow(TestImage):
# returns a two-part tuple with a tuple of column names and a tuple of # returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown. # data to be shown.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.images_mock.get.assert_called_with( self.client.find_image.assert_called_with(
self._image.id, self._image.id,
) )
@ -791,9 +754,9 @@ class TestImageShow(TestImage):
# returns a two-part tuple with a tuple of column names and a tuple of # returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown. # data to be shown.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.images_mock.get.assert_called_with( self.client.find_image.assert_called_with(
self._image.id, self._image.id,
) )
size_index = columns.index('size') size_index = columns.index('size')
self.assertEqual(data[size_index], '2K') self.assertEqual(data[size_index].human_readable(), '2K')

View File

@ -0,0 +1,4 @@
---
features:
- |
Complete switch from glanceclient to the SDK for image service.

View File

@ -10,7 +10,6 @@ openstacksdk>=0.36.0 # Apache-2.0
osc-lib>=2.0.0 # Apache-2.0 osc-lib>=2.0.0 # Apache-2.0
oslo.i18n>=3.15.3 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0
python-glanceclient>=2.8.0 # Apache-2.0
python-keystoneclient>=3.22.0 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0
python-novaclient>=15.1.0 # Apache-2.0 python-novaclient>=15.1.0 # Apache-2.0
python-cinderclient>=3.3.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0