diff --git a/novaclient/tests/functional/v2/legacy/test_readonly_nova.py b/novaclient/tests/functional/v2/legacy/test_readonly_nova.py index 80ff4f086..7f350199e 100644 --- a/novaclient/tests/functional/v2/legacy/test_readonly_nova.py +++ b/novaclient/tests/functional/v2/legacy/test_readonly_nova.py @@ -88,7 +88,8 @@ class SimpleReadOnlyNovaClientTest(base.ClientTestBase): self.nova('hypervisor-list') 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") def test_admin_interface_list(self): diff --git a/novaclient/tests/unit/v2/test_images.py b/novaclient/tests/unit/v2/test_images.py index 2c90e2dfc..bdafdf2cb 100644 --- a/novaclient/tests/unit/v2/test_images.py +++ b/novaclient/tests/unit/v2/test_images.py @@ -11,6 +11,10 @@ # License for the specific language governing permissions and limitations # under the License. +import warnings + +import mock + from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import images as data from novaclient.tests.unit import utils @@ -23,13 +27,15 @@ class ImagesTest(utils.FixturedTestCase): client_fixture_class = client.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() self.assert_request_id(il, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/images/detail') for i in il: self.assertIsInstance(i, images.Image) self.assertEqual(2, len(il)) + self.assertEqual(1, mock_warn.call_count) def test_list_images_undetailed(self): 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_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) self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/images/1') self.assertIsInstance(i, images.Image) self.assertEqual(1, i.id) 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) self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST) 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'}) self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST) 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'}) self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/images/1/metadata', {"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") self.assert_request_id(i, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual(1, i.id) 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') self.assert_request_id(iml, fakes.FAKE_REQUEST_ID_LIST) diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index 1258a6965..17ce6d670 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -883,11 +883,13 @@ class ShellTest(utils.TestCase): {'removeTenantAccess': {'tenant': 'proj2'}}) 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') 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', {'metadata': {'test_key': 'test_value'}}) @@ -950,7 +952,8 @@ class ShellTest(utils.TestCase): 'image-create sample-server mysnapshot_deleted --poll') 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') def test_image_delete_multiple(self): diff --git a/novaclient/v2/images.py b/novaclient/v2/images.py index 39e9dc06c..dbbc60ce0 100644 --- a/novaclient/v2/images.py +++ b/novaclient/v2/images.py @@ -13,9 +13,11 @@ # under the License. """ -Image interface. +DEPRECATED: Image interface. """ +import warnings + from six.moves.urllib import parse from novaclient import base @@ -23,7 +25,8 @@ from novaclient import base 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 @@ -32,7 +35,7 @@ class Image(base.Resource): def delete(self): """ - Delete this image. + DEPRECATED: Delete this image. :returns: An instance of novaclient.base.TupleWithMeta """ @@ -41,28 +44,36 @@ class Image(base.Resource): class ImageManager(base.ManagerWithFind): """ - Manage :class:`Image` resources. + DEPRECATED: Manage :class:`Image` resources. """ resource_class = Image def get(self, image): """ - Get an image. + DEPRECATED: Get an image. :param image: The ID of the image to get. :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") 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` :param limit: maximum number of images to return. :param marker: Begin returning images that appear later in the image 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 = {} detail = '' if detailed: @@ -77,7 +88,7 @@ class ImageManager(base.ManagerWithFind): def delete(self, image): """ - Delete an image. + DEPRECATED: Delete an image. It should go without saying that you can't delete an image that you didn't create. @@ -85,27 +96,39 @@ class ImageManager(base.ManagerWithFind): :param image: The :class:`Image` (or its ID) to delete. :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)) 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 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} return self._create("/images/%s/metadata" % base.getid(image), body, "metadata") 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 keys: A list of metadata keys to delete from the image :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) for k in keys: ret = self._delete("/images/%s/metadata/%s" % diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 8c28378eb..769725e42 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -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): try: (k, v) = text.split('=', 1) @@ -1201,7 +1209,8 @@ def do_network_create(cs, args): metavar="", help=_('Number of images to return per request.')) 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 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 ' 'delete).')) 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) metadata = _extract_metadata(args) @@ -1297,7 +1307,8 @@ def _print_flavor(flavor): metavar='', help=_("Name or ID of image.")) 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) _print_image(image) @@ -1306,7 +1317,8 @@ def do_image_show(cs, args): 'image', metavar='', nargs='+', help=_('Name or ID of image(s).')) 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: try: _find_image(cs, image).delete() diff --git a/releasenotes/notes/image-api-deprecation-41944dc6fc024918.yaml b/releasenotes/notes/image-api-deprecation-41944dc6fc024918.yaml new file mode 100644 index 000000000..23d987332 --- /dev/null +++ b/releasenotes/notes/image-api-deprecation-41944dc6fc024918.yaml @@ -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.