Add support for volume snapshot v2 command

openstack snapshot create
openstack snapshot set
openstack snapshot unset
openstack snapshot list

Implements: blueprint volume-v2
Change-Id: Ia1d9f4426baa0099281a9931f4eec99ebe1969b1
This commit is contained in:
Amey Bhide 2015-06-03 10:40:42 -07:00
parent 7e067c6f4f
commit c88b433abb
4 changed files with 401 additions and 6 deletions

View File

@ -12,6 +12,7 @@
# under the License.
#
import copy
import mock
from openstackclient.tests import fakes
@ -62,11 +63,17 @@ SNAPSHOT = {
"name": snapshot_name,
"description": snapshot_description,
"size": snapshot_size,
"metadata": snapshot_metadata
"status": "available",
"metadata": snapshot_metadata,
"created_at": "2015-06-03T18:49:19.000000",
"volume_id": volume_name
}
SNAPSHOT_columns = tuple(sorted(SNAPSHOT))
SNAPSHOT_data = tuple((SNAPSHOT[x] for x in sorted(SNAPSHOT)))
EXPECTED_SNAPSHOT = copy.deepcopy(SNAPSHOT)
EXPECTED_SNAPSHOT.pop("metadata")
EXPECTED_SNAPSHOT['properties'] = "foo='bar'"
SNAPSHOT_columns = tuple(sorted(EXPECTED_SNAPSHOT))
SNAPSHOT_data = tuple((EXPECTED_SNAPSHOT[x]
for x in sorted(EXPECTED_SNAPSHOT)))
type_id = "5520dc9e-6f9b-4378-a719-729911c0f407"

View File

@ -26,6 +26,53 @@ class TestSnapshot(volume_fakes.TestVolume):
self.snapshots_mock = self.app.client_manager.volume.volume_snapshots
self.snapshots_mock.reset_mock()
self.volumes_mock = self.app.client_manager.volume.volumes
self.volumes_mock.reset_mock()
class TestSnapshotCreate(TestSnapshot):
def setUp(self):
super(TestSnapshotCreate, self).setUp()
self.volumes_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.VOLUME),
loaded=True
)
self.snapshots_mock.create.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.SNAPSHOT),
loaded=True
)
# Get the command object to test
self.cmd = snapshot.CreateSnapshot(self.app, None)
def test_snapshot_create(self):
arglist = [
volume_fakes.volume_id,
"--name", volume_fakes.snapshot_name,
"--description", volume_fakes.snapshot_description,
"--force"
]
verifylist = [
("volume", volume_fakes.volume_id),
("name", volume_fakes.snapshot_name),
("description", volume_fakes.snapshot_description),
("force", True)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.snapshots_mock.create.assert_called_with(
volume_fakes.volume_id,
force=True,
name=volume_fakes.snapshot_name,
description=volume_fakes.snapshot_description
)
self.assertEqual(columns, volume_fakes.SNAPSHOT_columns)
self.assertEqual(data, volume_fakes.SNAPSHOT_data)
class TestSnapshotShow(TestSnapshot):
@ -80,3 +127,139 @@ class TestSnapshotDelete(TestSnapshot):
self.cmd.take_action(parsed_args)
self.snapshots_mock.delete.assert_called_with(volume_fakes.snapshot_id)
class TestSnapshotSet(TestSnapshot):
def setUp(self):
super(TestSnapshotSet, self).setUp()
self.snapshots_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.SNAPSHOT),
loaded=True
)
self.snapshots_mock.set_metadata.return_value = None
self.snapshots_mock.update.return_value = None
# Get the command object to mock
self.cmd = snapshot.SetSnapshot(self.app, None)
def test_snapshot_set(self):
arglist = [
volume_fakes.snapshot_id,
"--name", "new_snapshot",
"--property", "x=y",
"--property", "foo=foo"
]
new_property = {"x": "y", "foo": "foo"}
verifylist = [
("snapshot", volume_fakes.snapshot_id),
("name", "new_snapshot"),
("property", new_property)
]
kwargs = {
"name": "new_snapshot",
}
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.snapshots_mock.update.assert_called_with(
volume_fakes.snapshot_id, **kwargs)
self.snapshots_mock.set_metadata.assert_called_with(
volume_fakes.snapshot_id, new_property
)
class TestSnapshotUnset(TestSnapshot):
def setUp(self):
super(TestSnapshotUnset, self).setUp()
self.snapshots_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.SNAPSHOT),
loaded=True
)
self.snapshots_mock.delete_metadata.return_value = None
# Get the command object to mock
self.cmd = snapshot.UnsetSnapshot(self.app, None)
def test_snapshot_unset(self):
arglist = [
volume_fakes.snapshot_id,
"--property", "foo"
]
verifylist = [
("snapshot", volume_fakes.snapshot_id),
("property", ["foo"])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.snapshots_mock.delete_metadata.assert_called_with(
volume_fakes.snapshot_id, ["foo"]
)
class TestSnapshotList(TestSnapshot):
def setUp(self):
super(TestSnapshotList, self).setUp()
self.volumes_mock.list.return_value = [
fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.VOLUME),
loaded=True
)
]
self.snapshots_mock.list.return_value = [
fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.SNAPSHOT),
loaded=True
)
]
# Get the command to test
self.cmd = snapshot.ListSnapshot(self.app, None)
def test_snapshot_list_without_options(self):
arglist = []
verifylist = [
("long", False)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
collist = ["ID", "Name", "Description", "Status", "Size"]
self.assertEqual(collist, columns)
datalist = ((
volume_fakes.snapshot_id,
volume_fakes.snapshot_name,
volume_fakes.snapshot_description,
"available",
volume_fakes.snapshot_size
),)
self.assertEqual(datalist, tuple(data))
def test_snapshot_list_with_options(self):
arglist = ["--long"]
verifylist = [("long", True)]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
collist = ["ID", "Name", "Description", "Status", "Size", "Created At",
"Volume", "Properties"]
self.assertEqual(collist, columns)
datalist = ((
volume_fakes.snapshot_id,
volume_fakes.snapshot_name,
volume_fakes.snapshot_description,
"available",
volume_fakes.snapshot_size,
"2015-06-03T18:49:19.000000",
volume_fakes.volume_name,
volume_fakes.EXPECTED_SNAPSHOT.get("properties")
),)
self.assertEqual(datalist, tuple(data))

View File

@ -14,15 +14,67 @@
"""Volume v2 snapshot action implementations"""
import copy
import logging
from cliff import command
from cliff import lister
from cliff import show
import six
from openstackclient.common import parseractions
from openstackclient.common import utils
class CreateSnapshot(show.ShowOne):
"""Create new snapshot"""
log = logging.getLogger(__name__ + ".CreateSnapshot")
def get_parser(self, prog_name):
parser = super(CreateSnapshot, self).get_parser(prog_name)
parser.add_argument(
"volume",
metavar="<volume>",
help="Volume to snapshot (name or ID)"
)
parser.add_argument(
"--name",
metavar="<name>",
required=True,
help="Name of the snapshot"
)
parser.add_argument(
"--description",
metavar="<description>",
help="Description of the snapshot"
)
parser.add_argument(
"--force",
dest="force",
action="store_true",
default=False,
help="Create a snapshot attached to an instance. Default is False"
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action: (%s)", parsed_args)
volume_client = self.app.client_manager.volume
volume_id = utils.find_resource(
volume_client.volumes, parsed_args.volume).id
snapshot = volume_client.volume_snapshots.create(
volume_id,
force=parsed_args.force,
name=parsed_args.name,
description=parsed_args.description
)
snapshot._info.update(
{'properties': utils.format_dict(snapshot._info.pop('metadata'))}
)
return zip(*sorted(six.iteritems(snapshot._info)))
class DeleteSnapshot(command.Command):
"""Delete volume snapshot(s)"""
@ -34,7 +86,7 @@ class DeleteSnapshot(command.Command):
"snapshots",
metavar="<snapshot>",
nargs="+",
help="Snapsho(s) to delete (name or ID)"
help="Snapshot(s) to delete (name or ID)"
)
return parser
@ -48,6 +100,115 @@ class DeleteSnapshot(command.Command):
return
class ListSnapshot(lister.Lister):
"""List snapshots"""
log = logging.getLogger(__name__ + ".ListSnapshot")
def get_parser(self, prog_name):
parser = super(ListSnapshot, self).get_parser(prog_name)
parser.add_argument(
'--long',
action='store_true',
default=False,
help='List additional fields in output',
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action: (%s)", parsed_args)
def _format_volume_id(volume_id):
"""Return a volume name if available
:param volume_id: a volume ID
:rtype: either the volume ID or name
"""
volume = volume_id
if volume_id in volume_cache.keys():
volume = volume_cache[volume_id].name
return volume
if parsed_args.long:
columns = ['ID', 'Name', 'Description', 'Status',
'Size', 'Created At', 'Volume ID', 'Metadata']
column_headers = copy.deepcopy(columns)
column_headers[6] = 'Volume'
column_headers[7] = 'Properties'
else:
columns = ['ID', 'Name', 'Description', 'Status', 'Size']
column_headers = copy.deepcopy(columns)
# Cache the volume list
volume_cache = {}
try:
for s in self.app.client_manager.volume.volumes.list():
volume_cache[s.id] = s
except Exception:
# Just forget it if there's any trouble
pass
data = self.app.client_manager.volume.volume_snapshots.list()
return (column_headers,
(utils.get_item_properties(
s, columns,
formatters={'Metadata': utils.format_dict,
'Volume ID': _format_volume_id},
) for s in data))
class SetSnapshot(command.Command):
"""Set snapshot properties"""
log = logging.getLogger(__name__ + '.SetSnapshot')
def get_parser(self, prog_name):
parser = super(SetSnapshot, self).get_parser(prog_name)
parser.add_argument(
'snapshot',
metavar='<snapshot>',
help='Snapshot to modify (name or ID)')
parser.add_argument(
'--name',
metavar='<name>',
help='New snapshot name')
parser.add_argument(
'--description',
metavar='<description>',
help='New snapshot description')
parser.add_argument(
'--property',
metavar='<key=value>',
action=parseractions.KeyValueAction,
help='Property to add/change for this snapshot '
'(repeat option to set multiple properties)',
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
snapshot = utils.find_resource(volume_client.volume_snapshots,
parsed_args.snapshot)
kwargs = {}
if parsed_args.name:
kwargs['name'] = parsed_args.name
if parsed_args.description:
kwargs['description'] = parsed_args.description
if not kwargs and not parsed_args.property:
self.app.log.error("No changes requested\n")
return
if parsed_args.property:
volume_client.volume_snapshots.set_metadata(snapshot.id,
parsed_args.property)
volume_client.volume_snapshots.update(snapshot.id, **kwargs)
return
class ShowSnapshot(show.ShowOne):
"""Display snapshot details"""
@ -67,5 +228,45 @@ class ShowSnapshot(show.ShowOne):
volume_client = self.app.client_manager.volume
snapshot = utils.find_resource(
volume_client.volume_snapshots, parsed_args.snapshot)
snapshot = volume_client.volume_snapshots.get(snapshot.id)
snapshot._info.update(
{'properties': utils.format_dict(snapshot._info.pop('metadata'))}
)
return zip(*sorted(six.iteritems(snapshot._info)))
class UnsetSnapshot(command.Command):
"""Unset snapshot properties"""
log = logging.getLogger(__name__ + '.UnsetSnapshot')
def get_parser(self, prog_name):
parser = super(UnsetSnapshot, self).get_parser(prog_name)
parser.add_argument(
'snapshot',
metavar='<snapshot>',
help='Snapshot to modify (name or ID)',
)
parser.add_argument(
'--property',
metavar='<key>',
action='append',
default=[],
help='Property to remove from snapshot '
'(repeat to remove multiple values)',
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
volume_client = self.app.client_manager.volume
snapshot = utils.find_resource(
volume_client.volume_snapshots, parsed_args.snapshot)
if parsed_args.property:
volume_client.volume_snapshots.delete_metadata(
snapshot.id,
parsed_args.property,
)
else:
self.app.log.error("No changes requested\n")
return

View File

@ -370,8 +370,12 @@ openstack.volume.v2 =
backup_delete = openstackclient.volume.v2.backup:DeleteBackup
backup_show = openstackclient.volume.v2.backup:ShowBackup
snapshot_create = openstackclient.volume.v2.snapshot:CreateSnapshot
snapshot_delete = openstackclient.volume.v2.snapshot:DeleteSnapshot
snapshot_list = openstackclient.volume.v2.snapshot:ListSnapshot
snapshot_set = openstackclient.volume.v2.snapshot:SetSnapshot
snapshot_show = openstackclient.volume.v2.snapshot:ShowSnapshot
snapshot_unset = openstackclient.volume.v2.snapshot:UnsetSnapshot
volume_delete = openstackclient.volume.v2.volume:DeleteVolume
volume_show = openstackclient.volume.v2.volume:ShowVolume