Deprecate image list/show/delete/update CLIs/APIs

This deprecates all of the image CLIs/python API bindings that use
the Nova os-images API which is a proxy to the Glance v1 API.

This will emit a warning each time a deprecated CLI/API is used and also
updates the help docs for the deprecated CLIs and docstrings for APIs.

The plan is to do a release once this is merged so people start seeing
it and then we'll actually remove the deprecated CLIs/APIs in the first
python-novaclient release after the Nova server 15.0.0 'O' release.

Depends-On: Iff5fb3180855de7adb3399f6be16bedc8543b4ec

Change-Id: I3f60cc7f4c6e27861c4a84b925d573f35f1a1848
This commit is contained in:
Matt Riedemann 2016-04-06 16:52:05 -04:00
parent 77f214bdbd
commit a602e59806
6 changed files with 95 additions and 23 deletions

View File

@ -88,7 +88,8 @@ class SimpleReadOnlyNovaClientTest(base.ClientTestBase):
self.nova('hypervisor-list') self.nova('hypervisor-list')
def test_admin_image_list(self): def test_admin_image_list(self):
self.nova('image-list') out = self.nova('image-list', merge_stderr=True)
self.assertIn('Command image-list is deprecated', out)
@decorators.skip_because(bug="1157349") @decorators.skip_because(bug="1157349")
def test_admin_interface_list(self): def test_admin_interface_list(self):

View File

@ -11,6 +11,10 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import warnings
import mock
from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import images as data from novaclient.tests.unit.fixture_data import images as data
from novaclient.tests.unit import utils from novaclient.tests.unit import utils
@ -23,13 +27,15 @@ class ImagesTest(utils.FixturedTestCase):
client_fixture_class = client.V1 client_fixture_class = client.V1
data_fixture_class = data.V1 data_fixture_class = data.V1
def test_list_images(self): @mock.patch.object(warnings, 'warn')
def test_list_images(self, mock_warn):
il = self.cs.images.list() il = self.cs.images.list()
self.assert_request_id(il, fakes.FAKE_REQUEST_ID_LIST) self.assert_request_id(il, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('GET', '/images/detail') self.assert_called('GET', '/images/detail')
for i in il: for i in il:
self.assertIsInstance(i, images.Image) self.assertIsInstance(i, images.Image)
self.assertEqual(2, len(il)) self.assertEqual(2, len(il))
self.assertEqual(1, mock_warn.call_count)
def test_list_images_undetailed(self): def test_list_images_undetailed(self):
il = self.cs.images.list(detailed=False) il = self.cs.images.list(detailed=False)
@ -43,35 +49,48 @@ class ImagesTest(utils.FixturedTestCase):
self.assert_request_id(il, fakes.FAKE_REQUEST_ID_LIST) self.assert_request_id(il, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('GET', '/images/detail?limit=4&marker=1234') self.assert_called('GET', '/images/detail?limit=4&marker=1234')
def test_get_image_details(self): @mock.patch.object(warnings, 'warn')
def test_get_image_details(self, mock_warn):
i = self.cs.images.get(1) i = self.cs.images.get(1)
self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST) self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('GET', '/images/1') self.assert_called('GET', '/images/1')
self.assertIsInstance(i, images.Image) self.assertIsInstance(i, images.Image)
self.assertEqual(1, i.id) self.assertEqual(1, i.id)
self.assertEqual('CentOS 5.2', i.name) self.assertEqual('CentOS 5.2', i.name)
self.assertEqual(1, mock_warn.call_count)
def test_delete_image(self): @mock.patch.object(warnings, 'warn')
def test_delete_image(self, mock_warn):
i = self.cs.images.delete(1) i = self.cs.images.delete(1)
self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST) self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('DELETE', '/images/1') self.assert_called('DELETE', '/images/1')
self.assertEqual(1, mock_warn.call_count)
def test_delete_meta(self): @mock.patch.object(warnings, 'warn')
def test_delete_meta(self, mock_warn):
i = self.cs.images.delete_meta(1, {'test_key': 'test_value'}) i = self.cs.images.delete_meta(1, {'test_key': 'test_value'})
self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST) self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('DELETE', '/images/1/metadata/test_key') self.assert_called('DELETE', '/images/1/metadata/test_key')
self.assertEqual(1, mock_warn.call_count)
def test_set_meta(self): @mock.patch.object(warnings, 'warn')
def test_set_meta(self, mock_warn):
i = self.cs.images.set_meta(1, {'test_key': 'test_value'}) i = self.cs.images.set_meta(1, {'test_key': 'test_value'})
self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST) self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/images/1/metadata', self.assert_called('POST', '/images/1/metadata',
{"metadata": {'test_key': 'test_value'}}) {"metadata": {'test_key': 'test_value'}})
self.assertEqual(1, mock_warn.call_count)
def test_find(self): @mock.patch.object(warnings, 'warn')
def test_find(self, mock_warn):
i = self.cs.images.find(name="CentOS 5.2") i = self.cs.images.find(name="CentOS 5.2")
self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST) self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST)
self.assertEqual(1, i.id) self.assertEqual(1, i.id)
self.assert_called('GET', '/images/1') self.assert_called('GET', '/images/1')
# This is two warnings because find calls findall which calls list
# which is the first warning, which finds one results and then calls
# get on that, which is the second warning.
self.assertEqual(2, mock_warn.call_count)
iml = self.cs.images.findall(status='SAVING') iml = self.cs.images.findall(status='SAVING')
self.assert_request_id(iml, fakes.FAKE_REQUEST_ID_LIST) self.assert_request_id(iml, fakes.FAKE_REQUEST_ID_LIST)

View File

@ -883,11 +883,13 @@ class ShellTest(utils.TestCase):
{'removeTenantAccess': {'tenant': 'proj2'}}) {'removeTenantAccess': {'tenant': 'proj2'}})
def test_image_show(self): def test_image_show(self):
self.run_command('image-show 1') _, err = self.run_command('image-show 1')
self.assertIn('Command image-show is deprecated', err)
self.assert_called('GET', '/images/1') self.assert_called('GET', '/images/1')
def test_image_meta_set(self): def test_image_meta_set(self):
self.run_command('image-meta 1 set test_key=test_value') _, err = self.run_command('image-meta 1 set test_key=test_value')
self.assertIn('Command image-meta is deprecated', err)
self.assert_called('POST', '/images/1/metadata', self.assert_called('POST', '/images/1/metadata',
{'metadata': {'test_key': 'test_value'}}) {'metadata': {'test_key': 'test_value'}})
@ -950,7 +952,8 @@ class ShellTest(utils.TestCase):
'image-create sample-server mysnapshot_deleted --poll') 'image-create sample-server mysnapshot_deleted --poll')
def test_image_delete(self): def test_image_delete(self):
self.run_command('image-delete 1') _, err = self.run_command('image-delete 1')
self.assertIn('Command image-delete is deprecated', err)
self.assert_called('DELETE', '/images/1') self.assert_called('DELETE', '/images/1')
def test_image_delete_multiple(self): def test_image_delete_multiple(self):

View File

@ -13,9 +13,11 @@
# under the License. # under the License.
""" """
Image interface. DEPRECATED: Image interface.
""" """
import warnings
from six.moves.urllib import parse from six.moves.urllib import parse
from novaclient import base from novaclient import base
@ -23,7 +25,8 @@ from novaclient import base
class Image(base.Resource): class Image(base.Resource):
""" """
An image is a collection of files used to create or rebuild a server. DEPRECATED: An image is a collection of files used to create or rebuild a
server.
""" """
HUMAN_ID = True HUMAN_ID = True
@ -32,7 +35,7 @@ class Image(base.Resource):
def delete(self): def delete(self):
""" """
Delete this image. DEPRECATED: Delete this image.
:returns: An instance of novaclient.base.TupleWithMeta :returns: An instance of novaclient.base.TupleWithMeta
""" """
@ -41,28 +44,36 @@ class Image(base.Resource):
class ImageManager(base.ManagerWithFind): class ImageManager(base.ManagerWithFind):
""" """
Manage :class:`Image` resources. DEPRECATED: Manage :class:`Image` resources.
""" """
resource_class = Image resource_class = Image
def get(self, image): def get(self, image):
""" """
Get an image. DEPRECATED: Get an image.
:param image: The ID of the image to get. :param image: The ID of the image to get.
:rtype: :class:`Image` :rtype: :class:`Image`
""" """
warnings.warn(
'The novaclient.v2.images module is deprecated and will be '
'removed after Nova 15.0.0 is released. Use python-glanceclient '
'or python-openstacksdk instead.', DeprecationWarning)
return self._get("/images/%s" % base.getid(image), "image") return self._get("/images/%s" % base.getid(image), "image")
def list(self, detailed=True, limit=None, marker=None): def list(self, detailed=True, limit=None, marker=None):
""" """
Get a list of all images. DEPRECATED: Get a list of all images.
:rtype: list of :class:`Image` :rtype: list of :class:`Image`
:param limit: maximum number of images to return. :param limit: maximum number of images to return.
:param marker: Begin returning images that appear later in the image :param marker: Begin returning images that appear later in the image
list than that represented by this image id (optional). list than that represented by this image id (optional).
""" """
warnings.warn(
'The novaclient.v2.images module is deprecated and will be '
'removed after Nova 15.0.0 is released. Use python-glanceclient '
'or python-openstacksdk instead.', DeprecationWarning)
params = {} params = {}
detail = '' detail = ''
if detailed: if detailed:
@ -77,7 +88,7 @@ class ImageManager(base.ManagerWithFind):
def delete(self, image): def delete(self, image):
""" """
Delete an image. DEPRECATED: Delete an image.
It should go without saying that you can't delete an image It should go without saying that you can't delete an image
that you didn't create. that you didn't create.
@ -85,27 +96,39 @@ class ImageManager(base.ManagerWithFind):
:param image: The :class:`Image` (or its ID) to delete. :param image: The :class:`Image` (or its ID) to delete.
:returns: An instance of novaclient.base.TupleWithMeta :returns: An instance of novaclient.base.TupleWithMeta
""" """
warnings.warn(
'The novaclient.v2.images module is deprecated and will be '
'removed after Nova 15.0.0 is released. Use python-glanceclient '
'or python-openstacksdk instead.', DeprecationWarning)
return self._delete("/images/%s" % base.getid(image)) return self._delete("/images/%s" % base.getid(image))
def set_meta(self, image, metadata): def set_meta(self, image, metadata):
""" """
Set an images metadata DEPRECATED: Set an images metadata
:param image: The :class:`Image` to add metadata to :param image: The :class:`Image` to add metadata to
:param metadata: A dict of metadata to add to the image :param metadata: A dict of metadata to add to the image
""" """
warnings.warn(
'The novaclient.v2.images module is deprecated and will be '
'removed after Nova 15.0.0 is released. Use python-glanceclient '
'or python-openstacksdk instead.', DeprecationWarning)
body = {'metadata': metadata} body = {'metadata': metadata}
return self._create("/images/%s/metadata" % base.getid(image), return self._create("/images/%s/metadata" % base.getid(image),
body, "metadata") body, "metadata")
def delete_meta(self, image, keys): def delete_meta(self, image, keys):
""" """
Delete metadata from an image DEPRECATED: Delete metadata from an image
:param image: The :class:`Image` to delete metadata :param image: The :class:`Image` to delete metadata
:param keys: A list of metadata keys to delete from the image :param keys: A list of metadata keys to delete from the image
:returns: An instance of novaclient.base.TupleWithMeta :returns: An instance of novaclient.base.TupleWithMeta
""" """
warnings.warn(
'The novaclient.v2.images module is deprecated and will be '
'removed after Nova 15.0.0 is released. Use python-glanceclient '
'or python-openstacksdk instead.', DeprecationWarning)
result = base.TupleWithMeta((), None) result = base.TupleWithMeta((), None)
for k in keys: for k in keys:
ret = self._delete("/images/%s/metadata/%s" % ret = self._delete("/images/%s/metadata/%s" %

View File

@ -66,6 +66,14 @@ CLIENT_BDM2_KEYS = {
} }
# NOTE(mriedem): Remove this along with the deprecated commands in the first
# python-novaclient release AFTER the nova server 15.0.0 'O' release.
def emit_image_deprecation_warning(command_name):
print('WARNING: Command %s is deprecated and will be removed after Nova '
'15.0.0 is released. Use python-glanceclient or openstackclient '
'instead.' % command_name, file=sys.stderr)
def _key_value_pairing(text): def _key_value_pairing(text):
try: try:
(k, v) = text.split('=', 1) (k, v) = text.split('=', 1)
@ -1201,7 +1209,8 @@ def do_network_create(cs, args):
metavar="<limit>", metavar="<limit>",
help=_('Number of images to return per request.')) help=_('Number of images to return per request.'))
def do_image_list(cs, _args): def do_image_list(cs, _args):
"""Print a list of available images to boot from.""" """DEPRECATED: Print a list of available images to boot from."""
emit_image_deprecation_warning('image-list')
limit = _args.limit limit = _args.limit
image_list = cs.images.list(limit=limit) image_list = cs.images.list(limit=limit)
@ -1234,7 +1243,8 @@ def do_image_list(cs, _args):
help=_('Metadata to add/update or delete (only key is necessary on ' help=_('Metadata to add/update or delete (only key is necessary on '
'delete).')) 'delete).'))
def do_image_meta(cs, args): def do_image_meta(cs, args):
"""Set or delete metadata on an image.""" """DEPRECATED: Set or delete metadata on an image."""
emit_image_deprecation_warning('image-meta')
image = _find_image(cs, args.image) image = _find_image(cs, args.image)
metadata = _extract_metadata(args) metadata = _extract_metadata(args)
@ -1297,7 +1307,8 @@ def _print_flavor(flavor):
metavar='<image>', metavar='<image>',
help=_("Name or ID of image.")) help=_("Name or ID of image."))
def do_image_show(cs, args): def do_image_show(cs, args):
"""Show details about the given image.""" """DEPRECATED: Show details about the given image."""
emit_image_deprecation_warning('image-show')
image = _find_image(cs, args.image) image = _find_image(cs, args.image)
_print_image(image) _print_image(image)
@ -1306,7 +1317,8 @@ def do_image_show(cs, args):
'image', metavar='<image>', nargs='+', 'image', metavar='<image>', nargs='+',
help=_('Name or ID of image(s).')) help=_('Name or ID of image(s).'))
def do_image_delete(cs, args): def do_image_delete(cs, args):
"""Delete specified image(s).""" """DEPRECATED: Delete specified image(s)."""
emit_image_deprecation_warning('image-delete')
for image in args.image: for image in args.image:
try: try:
_find_image(cs, image).delete() _find_image(cs, image).delete()

View File

@ -0,0 +1,14 @@
---
deprecations:
- |
The following CLIs and python API bindings are now deprecated for removal:
* nova image-delete
* nova image-list
* nova image-meta
* nova image-show
These will be removed in the first major python-novaclient release after
the Nova 15.0.0 Ocata release. Use python-glanceclient or
python-openstackclient for CLI and python-glanceclient or openstacksdk
for python API bindings.