Deprecate volume/volume-type/volume-snapshot CRUD CLIs/APIs

This deprecates all of the volume CLIs/APIs that go directly to the
cinder API via the volume service type.

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 2016.1 'M' release.

DocImpact: The volume, volume-type and volume-snapshot CRUD CLIs/APIs are
           deprecated and will be removed after the Nova 2016.1 release.
           Use python-cinderclient or openstackclient for CLIs instead.
           Use python-cinderclient or python-openstacksdk for APIs instead.

Related-Bug: #1454369

Change-Id: I4e92f924739de9b664e08117984bfb60359f2212
This commit is contained in:
Matt Riedemann 2015-05-22 13:34:51 -07:00
parent 2fad863909
commit 23f13437dd
7 changed files with 149 additions and 54 deletions

View File

@ -134,10 +134,12 @@ class SimpleReadOnlyNovaClientTest(base.ClientTestBase):
self.nova('volume-list')
def test_admin_volume_snapshot_list(self):
self.nova('volume-snapshot-list')
out = self.nova('volume-snapshot-list', merge_stderr=True)
self.assertIn('Command volume-snapshot-list is deprecated', out)
def test_admin_volume_type_list(self):
self.nova('volume-type-list')
out = self.nova('volume-type-list', merge_stderr=True)
self.assertIn('Command volume-type-list is deprecated', out)
def test_admin_help(self):
self.nova('help')

View File

@ -74,12 +74,13 @@ class ShellTest(utils.TestCase):
lambda *_: fakes.FakeClient))
@mock.patch('sys.stdout', new_callable=six.StringIO)
def run_command(self, cmd, mock_stdout):
@mock.patch('sys.stderr', new_callable=six.StringIO)
def run_command(self, cmd, mock_stderr, mock_stdout):
if isinstance(cmd, list):
self.shell.main(cmd)
else:
self.shell.main(cmd.split())
return mock_stdout.getvalue()
return mock_stdout.getvalue(), mock_stderr.getvalue()
def assert_called(self, method, url, body=None, **kwargs):
return self.shell.cs.assert_called(method, url, body, **kwargs)
@ -803,7 +804,7 @@ class ShellTest(utils.TestCase):
)
def test_create_image_show(self):
output = self.run_command(
output, _ = self.run_command(
'image-create sample-server mysnapshot --show')
self.assert_called_anytime(
'POST', '/servers/1234/action',
@ -908,7 +909,7 @@ class ShellTest(utils.TestCase):
mock.ANY, mock.ANY, mock.ANY, sortby_index=1)
def test_list_fields(self):
output = self.run_command(
output, _ = self.run_command(
'list --fields '
'host,security_groups,OS-EXT-MOD:some_thing')
self.assert_called('GET', '/servers/detail')
@ -926,7 +927,7 @@ class ShellTest(utils.TestCase):
{'reboot': {'type': 'HARD'}})
def test_rebuild(self):
output = self.run_command('rebuild sample-server 1')
output, _ = self.run_command('rebuild sample-server 1')
self.assert_called('GET', '/servers?name=sample-server', pos=-6)
self.assert_called('GET', '/servers/1234', pos=-5)
self.assert_called('GET', '/images/1', pos=-4)
@ -937,8 +938,8 @@ class ShellTest(utils.TestCase):
self.assertIn('adminPass', output)
def test_rebuild_password(self):
output = self.run_command('rebuild sample-server 1'
' --rebuild-password asdf')
output, _ = self.run_command('rebuild sample-server 1'
' --rebuild-password asdf')
self.assert_called('GET', '/servers?name=sample-server', pos=-6)
self.assert_called('GET', '/servers/1234', pos=-5)
self.assert_called('GET', '/images/1', pos=-4)
@ -1091,7 +1092,7 @@ class ShellTest(utils.TestCase):
self.run_command, 'show xxx')
def test_show_unavailable_image_and_flavor(self):
output = self.run_command('show 9013')
output, _ = self.run_command('show 9013')
self.assert_called('GET', '/servers/9013', pos=-8)
self.assert_called('GET',
'/flavors/80645cf4-6ad3-410a-bbc8-6f3e1e291f51',
@ -1860,7 +1861,7 @@ class ShellTest(utils.TestCase):
self.assert_called('GET', '/os-networks')
def test_network_list_fields(self):
output = self.run_command(
output, _ = self.run_command(
'network-list --fields '
'vlan,project_id')
self.assert_called('GET', '/os-networks')
@ -2037,7 +2038,7 @@ class ShellTest(utils.TestCase):
self.run_command('limits --tenant 1234')
self.assert_called('GET', '/limits?tenant_id=1234')
stdout = self.run_command('limits --tenant 1234')
stdout, _ = self.run_command('limits --tenant 1234')
self.assertIn('Verb', stdout)
self.assertIn('Name', stdout)
@ -2190,11 +2191,13 @@ class ShellTest(utils.TestCase):
self.assert_called('DELETE', '/servers/1234/os-interface/port_id')
def test_volume_list(self):
self.run_command('volume-list')
_, err = self.run_command('volume-list')
self.assertIn('Command volume-list is deprecated', err)
self.assert_called('GET', '/volumes/detail')
def test_volume_show(self):
self.run_command('volume-show Work')
_, err = self.run_command('volume-show Work')
self.assertIn('Command volume-show is deprecated', err)
self.assert_called('GET', '/volumes?display_name=Work', pos=-2)
self.assert_called(
'GET',
@ -2203,7 +2206,8 @@ class ShellTest(utils.TestCase):
)
def test_volume_create(self):
self.run_command('volume-create 2 --display-name Work')
_, err = self.run_command('volume-create 2 --display-name Work')
self.assertIn('Command volume-create is deprecated', err)
self.assert_called('POST', '/volumes',
{'volume':
{'display_name': 'Work',
@ -2215,7 +2219,8 @@ class ShellTest(utils.TestCase):
'size': 2}})
def test_volume_delete(self):
self.run_command('volume-delete Work')
_, err = self.run_command('volume-delete Work')
self.assertIn('Command volume-delete is deprecated', err)
self.assert_called('DELETE',
'/volumes/15e59938-07d5-11e1-90e3-e3dffe0c5983')

View File

@ -13,6 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import warnings
import mock
from novaclient.tests.unit import utils
from novaclient.tests.unit.v2 import fakes
from novaclient.v2 import volumes
@ -23,26 +27,33 @@ cs = fakes.FakeClient()
class VolumesTest(utils.TestCase):
def test_list_servers(self):
@mock.patch.object(warnings, 'warn')
def test_list_volumes(self, mock_warn):
vl = cs.volumes.list()
cs.assert_called('GET', '/volumes/detail')
for v in vl:
self.assertIsInstance(v, volumes.Volume)
self.assertEqual(1, mock_warn.call_count)
def test_list_volumes_undetailed(self):
@mock.patch.object(warnings, 'warn')
def test_list_volumes_undetailed(self, mock_warn):
vl = cs.volumes.list(detailed=False)
cs.assert_called('GET', '/volumes')
for v in vl:
self.assertIsInstance(v, volumes.Volume)
self.assertEqual(1, mock_warn.call_count)
def test_get_volume_details(self):
@mock.patch.object(warnings, 'warn')
def test_get_volume_details(self, mock_warn):
vol_id = '15e59938-07d5-11e1-90e3-e3dffe0c5983'
v = cs.volumes.get(vol_id)
cs.assert_called('GET', '/volumes/%s' % vol_id)
self.assertIsInstance(v, volumes.Volume)
self.assertEqual(v.id, vol_id)
self.assertEqual(1, mock_warn.call_count)
def test_create_volume(self):
@mock.patch.object(warnings, 'warn')
def test_create_volume(self, mock_warn):
v = cs.volumes.create(
size=2,
display_name="My volume",
@ -50,8 +61,10 @@ class VolumesTest(utils.TestCase):
)
cs.assert_called('POST', '/volumes')
self.assertIsInstance(v, volumes.Volume)
self.assertEqual(1, mock_warn.call_count)
def test_delete_volume(self):
@mock.patch.object(warnings, 'warn')
def test_delete_volume(self, mock_warn):
vol_id = '15e59938-07d5-11e1-90e3-e3dffe0c5983'
v = cs.volumes.get(vol_id)
v.delete()
@ -60,6 +73,7 @@ class VolumesTest(utils.TestCase):
cs.assert_called('DELETE', '/volumes/%s' % vol_id)
cs.volumes.delete(v)
cs.assert_called('DELETE', '/volumes/%s' % vol_id)
self.assertEqual(4, mock_warn.call_count)
def test_create_server_volume(self):
v = cs.volumes.create_server_volume(

View File

@ -61,6 +61,14 @@ CLIENT_BDM2_KEYS = {
}
# NOTE(mriedem): Remove this along with the deprecated commands in the first
# python-novaclient release AFTER the nova server 2016.1 'M' release.
def emit_volume_deprecation_warning(command_name):
print('WARNING: Command %s is deprecated and will be removed after Nova '
'2016.1 is released. Use python-cinderclient or openstackclient '
'instead.' % command_name, file=sys.stderr)
def _key_value_pairing(text):
try:
(k, v) = text.split('=', 1)
@ -1986,7 +1994,8 @@ def _translate_availability_zone_keys(collection):
const=1,
help=argparse.SUPPRESS)
def do_volume_list(cs, args):
"""List all the volumes."""
"""DEPRECATED: List all the volumes."""
emit_volume_deprecation_warning('volume-list')
search_opts = {'all_tenants': args.all_tenants}
volumes = cs.volumes.list(search_opts=search_opts)
_translate_volume_keys(volumes)
@ -2004,7 +2013,8 @@ def do_volume_list(cs, args):
metavar='<volume>',
help=_('Name or ID of the volume.'))
def do_volume_show(cs, args):
"""Show details about a volume."""
"""DEPRECATED: Show details about a volume."""
emit_volume_deprecation_warning('volume-show')
volume = _find_volume(cs, args.volume)
_print_volume(volume)
@ -2056,7 +2066,8 @@ def do_volume_show(cs, args):
help=_('Optional Availability Zone for volume. (Default=None)'),
default=None)
def do_volume_create(cs, args):
"""Add a new volume."""
"""DEPRECATED: Add a new volume."""
emit_volume_deprecation_warning('volume-create')
volume = cs.volumes.create(args.size,
args.snapshot_id,
args.display_name,
@ -2072,7 +2083,8 @@ def do_volume_create(cs, args):
metavar='<volume>', nargs='+',
help=_('Name or ID of the volume(s) to delete.'))
def do_volume_delete(cs, args):
"""Remove volume(s)."""
"""DEPRECATED: Remove volume(s)."""
emit_volume_deprecation_warning('volume-delete')
for volume in args.volume:
try:
_find_volume(cs, volume).delete()
@ -2138,7 +2150,8 @@ def do_volume_detach(cs, args):
def do_volume_snapshot_list(cs, _args):
"""List all the snapshots."""
"""DEPRECATED: List all the snapshots."""
emit_volume_deprecation_warning('volume-snapshot-list')
snapshots = cs.volume_snapshots.list()
_translate_volume_snapshot_keys(snapshots)
utils.print_list(snapshots, ['ID', 'Volume ID', 'Status', 'Display Name',
@ -2150,7 +2163,8 @@ def do_volume_snapshot_list(cs, _args):
metavar='<snapshot>',
help=_('Name or ID of the snapshot.'))
def do_volume_snapshot_show(cs, args):
"""Show details about a snapshot."""
"""DEPRECATED: Show details about a snapshot."""
emit_volume_deprecation_warning('volume-snapshot-show')
snapshot = _find_volume_snapshot(cs, args.snapshot)
_print_volume_snapshot(snapshot)
@ -2182,7 +2196,8 @@ def do_volume_snapshot_show(cs, args):
'--display_description',
help=argparse.SUPPRESS)
def do_volume_snapshot_create(cs, args):
"""Add a new snapshot."""
"""DEPRECATED: Add a new snapshot."""
emit_volume_deprecation_warning('volume-snapshot-create')
snapshot = cs.volume_snapshots.create(args.volume_id,
args.force,
args.display_name,
@ -2195,7 +2210,8 @@ def do_volume_snapshot_create(cs, args):
metavar='<snapshot>',
help=_('Name or ID of the snapshot to delete.'))
def do_volume_snapshot_delete(cs, args):
"""Remove a snapshot."""
"""DEPRECATED: Remove a snapshot."""
emit_volume_deprecation_warning('volume-snapshot-delete')
snapshot = _find_volume_snapshot(cs, args.snapshot)
snapshot.delete()
@ -2205,7 +2221,8 @@ def _print_volume_type_list(vtypes):
def do_volume_type_list(cs, args):
"""Print a list of available 'volume types'."""
"""DEPRECATED: Print a list of available 'volume types'."""
emit_volume_deprecation_warning('volume-type-list')
vtypes = cs.volume_types.list()
_print_volume_type_list(vtypes)
@ -2215,7 +2232,8 @@ def do_volume_type_list(cs, args):
metavar='<name>',
help=_("Name of the new volume type"))
def do_volume_type_create(cs, args):
"""Create a new volume type."""
"""DEPRECATED: Create a new volume type."""
emit_volume_deprecation_warning('volume-type-create')
vtype = cs.volume_types.create(args.name)
_print_volume_type_list([vtype])
@ -2225,7 +2243,8 @@ def do_volume_type_create(cs, args):
metavar='<id>',
help=_("Unique ID of the volume type to delete"))
def do_volume_type_delete(cs, args):
"""Delete a specific volume type."""
"""DEPRECATED: Delete a specific volume type."""
emit_volume_deprecation_warning('volume-type-delete')
cs.volume_types.delete(args.id)

View File

@ -14,15 +14,17 @@
# under the License.
"""
Volume snapshot interface (1.1 extension).
DEPRECATED: Volume snapshot interface (1.1 extension).
"""
import warnings
from novaclient import base
class Snapshot(base.Resource):
"""
A Snapshot is a point-in-time snapshot of an openstack volume.
DEPRECATED: A Snapshot is a point-in-time snapshot of an openstack volume.
"""
NAME_ATTR = 'display_name'
@ -31,14 +33,14 @@ class Snapshot(base.Resource):
def delete(self):
"""
Delete this snapshot.
DEPRECATED: Delete this snapshot.
"""
self.manager.delete(self)
class SnapshotManager(base.ManagerWithFind):
"""
Manage :class:`Snapshot` resources.
DEPRECATED: Manage :class:`Snapshot` resources.
"""
resource_class = Snapshot
@ -46,7 +48,7 @@ class SnapshotManager(base.ManagerWithFind):
display_description=None):
"""
Create a snapshot of the given volume.
DEPRECATED: Create a snapshot of the given volume.
:param volume_id: The ID of the volume to snapshot.
:param force: If force is True, create a snapshot even if the volume is
@ -55,6 +57,10 @@ class SnapshotManager(base.ManagerWithFind):
:param display_description: Description of the snapshot
:rtype: :class:`Snapshot`
"""
warnings.warn('The novaclient.v2.volume_snapshots module is '
'deprecated and will be removed after Nova 2016.1 is '
'released. Use python-cinderclient or '
'python-openstacksdk instead.', DeprecationWarning)
with self.alternate_service_type('volume'):
body = {'snapshot': {'volume_id': volume_id,
'force': force,
@ -64,20 +70,28 @@ class SnapshotManager(base.ManagerWithFind):
def get(self, snapshot_id):
"""
Get a snapshot.
DEPRECATED: Get a snapshot.
:param snapshot_id: The ID of the snapshot to get.
:rtype: :class:`Snapshot`
"""
warnings.warn('The novaclient.v2.volume_snapshots module is '
'deprecated and will be removed after Nova 2016.1 is '
'released. Use python-cinderclient or '
'python-openstacksdk instead.', DeprecationWarning)
with self.alternate_service_type('volume'):
return self._get("/snapshots/%s" % snapshot_id, "snapshot")
def list(self, detailed=True):
"""
Get a list of all snapshots.
DEPRECATED: Get a list of all snapshots.
:rtype: list of :class:`Snapshot`
"""
warnings.warn('The novaclient.v2.volume_snapshots module is '
'deprecated and will be removed after Nova 2016.1 is '
'released. Use python-cinderclient or '
'python-openstacksdk instead.', DeprecationWarning)
with self.alternate_service_type('volume'):
if detailed is True:
return self._list("/snapshots/detail", "snapshots")
@ -86,9 +100,13 @@ class SnapshotManager(base.ManagerWithFind):
def delete(self, snapshot):
"""
Delete a snapshot.
DEPRECATED: Delete a snapshot.
:param snapshot: The :class:`Snapshot` to delete.
"""
warnings.warn('The novaclient.v2.volume_snapshots module is '
'deprecated and will be removed after Nova 2016.1 is '
'released. Use python-cinderclient or '
'python-openstacksdk instead.', DeprecationWarning)
with self.alternate_service_type('volume'):
self._delete("/snapshots/%s" % base.getid(snapshot))

View File

@ -15,15 +15,17 @@
"""
Volume Type interface.
DEPRECATED: Volume Type interface.
"""
import warnings
from novaclient import base
class VolumeType(base.Resource):
"""
A Volume Type is the type of volume to be created
DEPRECATED: A Volume Type is the type of volume to be created
"""
def __repr__(self):
return "<Volume Type: %s>" % self.name
@ -31,46 +33,62 @@ class VolumeType(base.Resource):
class VolumeTypeManager(base.ManagerWithFind):
"""
Manage :class:`VolumeType` resources.
DEPRECATED: Manage :class:`VolumeType` resources.
"""
resource_class = VolumeType
def list(self):
"""
Get a list of all volume types.
DEPRECATED: Get a list of all volume types.
:rtype: list of :class:`VolumeType`.
"""
warnings.warn('The novaclient.v2.volume_types module is deprecated '
'and will be removed after Nova 2016.1 is released. Use '
'python-cinderclient or python-openstacksdk instead.',
DeprecationWarning)
with self.alternate_service_type('volume'):
return self._list("/types", "volume_types")
def get(self, volume_type):
"""
Get a specific volume type.
DEPRECATED: Get a specific volume type.
:param volume_type: The ID of the :class:`VolumeType` to get.
:rtype: :class:`VolumeType`
"""
warnings.warn('The novaclient.v2.volume_types module is deprecated '
'and will be removed after Nova 2016.1 is released. Use '
'python-cinderclient or python-openstacksdk instead.',
DeprecationWarning)
with self.alternate_service_type('volume'):
return self._get("/types/%s" % base.getid(volume_type),
"volume_type")
def delete(self, volume_type):
"""
Delete a specific volume_type.
DEPRECATED: Delete a specific volume_type.
:param volume_type: The ID of the :class:`VolumeType` to get.
"""
warnings.warn('The novaclient.v2.volume_types module is deprecated '
'and will be removed after Nova 2016.1 is released. Use '
'python-cinderclient or python-openstacksdk instead.',
DeprecationWarning)
with self.alternate_service_type('volume'):
self._delete("/types/%s" % base.getid(volume_type))
def create(self, name):
"""
Create a volume type.
DEPRECATED: Create a volume type.
:param name: Descriptive name of the volume type
:rtype: :class:`VolumeType`
"""
warnings.warn('The novaclient.v2.volume_types module is deprecated '
'and will be removed after Nova 2016.1 is released. Use '
'python-cinderclient or python-openstacksdk instead.',
DeprecationWarning)
with self.alternate_service_type('volume'):
body = {
"volume_type": {

View File

@ -17,6 +17,8 @@
Volume interface (1.1 extension).
"""
import warnings
import six
from six.moves.urllib import parse
@ -25,7 +27,8 @@ from novaclient import base
class Volume(base.Resource):
"""
A volume is an extra block level storage to the OpenStack instances.
DEPRECATED: A volume is an extra block level storage to the OpenStack
instances.
"""
NAME_ATTR = 'display_name'
@ -34,14 +37,14 @@ class Volume(base.Resource):
def delete(self):
"""
Delete this volume.
DEPRECATED: Delete this volume.
"""
self.manager.delete(self)
class VolumeManager(base.ManagerWithFind):
"""
Manage :class:`Volume` resources.
DEPRECATED: Manage :class:`Volume` resources.
"""
resource_class = Volume
@ -49,7 +52,7 @@ class VolumeManager(base.ManagerWithFind):
display_description=None, volume_type=None,
availability_zone=None, imageRef=None):
"""
Create a volume.
DEPRECATED: Create a volume.
:param size: Size of volume in GB
:param snapshot_id: ID of the snapshot
@ -60,6 +63,10 @@ class VolumeManager(base.ManagerWithFind):
:rtype: :class:`Volume`
:param imageRef: reference to an image stored in glance
"""
warnings.warn('The novaclient.v2.volumes.VolumeManager.create() '
'method is deprecated and will be removed after Nova '
'2016.1 is released. Use python-cinderclient or '
'python-openstacksdk instead.', DeprecationWarning)
# NOTE(melwitt): Ensure we use the volume endpoint for this call
with self.alternate_service_type('volume'):
body = {'volume': {'size': size,
@ -73,20 +80,28 @@ class VolumeManager(base.ManagerWithFind):
def get(self, volume_id):
"""
Get a volume.
DEPRECATED: Get a volume.
:param volume_id: The ID of the volume to get.
:rtype: :class:`Volume`
"""
warnings.warn('The novaclient.v2.volumes.VolumeManager.get() '
'method is deprecated and will be removed after Nova '
'2016.1 is released. Use python-cinderclient or '
'python-openstacksdk instead.', DeprecationWarning)
with self.alternate_service_type('volume'):
return self._get("/volumes/%s" % volume_id, "volume")
def list(self, detailed=True, search_opts=None):
"""
Get a list of all volumes.
DEPRECATED: Get a list of all volumes.
:rtype: list of :class:`Volume`
"""
warnings.warn('The novaclient.v2.volumes.VolumeManager.list() '
'method is deprecated and will be removed after Nova '
'2016.1 is released. Use python-cinderclient or '
'python-openstacksdk instead.', DeprecationWarning)
with self.alternate_service_type('volume'):
search_opts = search_opts or {}
@ -105,10 +120,14 @@ class VolumeManager(base.ManagerWithFind):
def delete(self, volume):
"""
Delete a volume.
DEPRECATED: Delete a volume.
:param volume: The :class:`Volume` to delete.
"""
warnings.warn('The novaclient.v2.volumes.VolumeManager.delete() '
'method is deprecated and will be removed after Nova '
'2016.1 is released. Use python-cinderclient or '
'python-openstacksdk instead.', DeprecationWarning)
with self.alternate_service_type('volume'):
self._delete("/volumes/%s" % base.getid(volume))