Add support for volume backup v2 command

openstack backup create
openstack backup list
openstack backup restore

Implements: blueprint volume-v2
Change-Id: I77965730065dd44f256c46bcc43c1e6a03b63145
This commit is contained in:
Amey Bhide 2015-06-03 15:05:18 -07:00
parent 7f658c0aca
commit 2fce863411
4 changed files with 288 additions and 1 deletions
openstackclient
tests/volume/v2
volume/v2
setup.cfg

@ -93,6 +93,7 @@ backup_description = "fake description"
backup_object_count = None backup_object_count = None
backup_container = None backup_container = None
backup_size = 10 backup_size = 10
backup_status = "error"
BACKUP = { BACKUP = {
"id": backup_id, "id": backup_id,
@ -101,7 +102,9 @@ BACKUP = {
"description": backup_description, "description": backup_description,
"object_count": backup_object_count, "object_count": backup_object_count,
"container": backup_container, "container": backup_container,
"size": backup_size "size": backup_size,
"status": backup_status,
"availability_zone": volume_availability_zone,
} }
BACKUP_columns = tuple(sorted(BACKUP)) BACKUP_columns = tuple(sorted(BACKUP))
@ -118,6 +121,8 @@ class FakeVolumeClient(object):
self.backups.resource_class = fakes.FakeResource(None, {}) self.backups.resource_class = fakes.FakeResource(None, {})
self.volume_types = mock.Mock() self.volume_types = mock.Mock()
self.volume_types.resource_class = fakes.FakeResource(None, {}) self.volume_types.resource_class = fakes.FakeResource(None, {})
self.restores = mock.Mock()
self.restores.resource_class = fakes.FakeResource(None, {})
self.auth_token = kwargs['token'] self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint'] self.management_url = kwargs['endpoint']

@ -26,6 +26,55 @@ class TestBackup(volume_fakes.TestVolume):
self.backups_mock = self.app.client_manager.volume.backups self.backups_mock = self.app.client_manager.volume.backups
self.backups_mock.reset_mock() self.backups_mock.reset_mock()
self.volumes_mock = self.app.client_manager.volume.volumes
self.volumes_mock.reset_mock()
self.restores_mock = self.app.client_manager.volume.restores
self.restores_mock.reset_mock()
class TestBackupCreate(TestBackup):
def setUp(self):
super(TestBackupCreate, self).setUp()
self.volumes_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.VOLUME),
loaded=True
)
self.backups_mock.create.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.BACKUP),
loaded=True
)
# Get the command object to test
self.cmd = backup.CreateBackup(self.app, None)
def test_backup_create(self):
arglist = [
volume_fakes.volume_id,
"--name", volume_fakes.backup_name,
"--description", volume_fakes.backup_description,
"--container", volume_fakes.backup_name
]
verifylist = [
("volume", volume_fakes.volume_id),
("name", volume_fakes.backup_name),
("description", volume_fakes.backup_description),
("container", volume_fakes.backup_name)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.backups_mock.create.assert_called_with(
volume_fakes.volume_id,
container=volume_fakes.backup_name,
name=volume_fakes.backup_name,
description=volume_fakes.backup_description
)
self.assertEqual(columns, volume_fakes.BACKUP_columns)
self.assertEqual(data, volume_fakes.BACKUP_data)
class TestBackupShow(TestBackup): class TestBackupShow(TestBackup):
@ -80,3 +129,101 @@ class TestBackupDelete(TestBackup):
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
self.backups_mock.delete.assert_called_with(volume_fakes.backup_id) self.backups_mock.delete.assert_called_with(volume_fakes.backup_id)
class TestBackupRestore(TestBackup):
def setUp(self):
super(TestBackupRestore, self).setUp()
self.backups_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.BACKUP),
loaded=True
)
self.volumes_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.VOLUME),
loaded=True
)
self.restores_mock.restore.return_value = None
# Get the command object to mock
self.cmd = backup.RestoreBackup(self.app, None)
def test_backup_restore(self):
arglist = [
volume_fakes.backup_id,
volume_fakes.volume_id
]
verifylist = [
("backup", volume_fakes.backup_id),
("volume", volume_fakes.volume_id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.restores_mock.restore.assert_called_with(volume_fakes.backup_id,
volume_fakes.volume_id)
class TestBackupList(TestBackup):
def setUp(self):
super(TestBackupList, self).setUp()
self.volumes_mock.list.return_value = [
fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.VOLUME),
loaded=True
)
]
self.backups_mock.list.return_value = [
fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.BACKUP),
loaded=True
)
]
# Get the command to test
self.cmd = backup.ListBackup(self.app, None)
def test_backup_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.backup_id,
volume_fakes.backup_name,
volume_fakes.backup_description,
volume_fakes.backup_status,
volume_fakes.backup_size
),)
self.assertEqual(datalist, tuple(data))
def test_backup_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',
'Availability Zone', 'Volume', 'Container']
self.assertEqual(collist, columns)
datalist = ((
volume_fakes.backup_id,
volume_fakes.backup_name,
volume_fakes.backup_description,
volume_fakes.backup_status,
volume_fakes.backup_size,
volume_fakes.volume_availability_zone,
volume_fakes.backup_volume_id,
volume_fakes.backup_container
),)
self.assertEqual(datalist, tuple(data))

@ -14,15 +14,62 @@
"""Volume v2 Backup action implementations""" """Volume v2 Backup action implementations"""
import copy
import logging import logging
from cliff import command from cliff import command
from cliff import lister
from cliff import show from cliff import show
import six import six
from openstackclient.common import utils from openstackclient.common import utils
class CreateBackup(show.ShowOne):
"""Create new backup"""
log = logging.getLogger(__name__ + ".CreateBackup")
def get_parser(self, prog_name):
parser = super(CreateBackup, self).get_parser(prog_name)
parser.add_argument(
"volume",
metavar="<volume>",
help="Volume to backup (name or ID)"
)
parser.add_argument(
"--name",
metavar="<name>",
required=True,
help="Name of the backup"
)
parser.add_argument(
"--description",
metavar="<description>",
help="Description of the backup"
)
parser.add_argument(
"--container",
metavar="<container>",
help="Optional backup container name"
)
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
backup = volume_client.backups.create(
volume_id,
container=parsed_args.container,
name=parsed_args.name,
description=parsed_args.description
)
backup._info.pop("links", None)
return zip(*sorted(six.iteritems(backup._info)))
class DeleteBackup(command.Command): class DeleteBackup(command.Command):
"""Delete backup(s)""" """Delete backup(s)"""
@ -48,6 +95,91 @@ class DeleteBackup(command.Command):
return return
class ListBackup(lister.Lister):
"""List backups"""
log = logging.getLogger(__name__ + ".ListBackup")
def get_parser(self, prog_name):
parser = super(ListBackup, 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',
'Availability Zone', 'Volume ID', 'Container']
column_headers = copy.deepcopy(columns)
column_headers[6] = 'Volume'
else:
columns = ['ID', 'Name', 'Description', 'Status', 'Size']
column_headers = 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.backups.list()
return (column_headers,
(utils.get_item_properties(
s, columns,
formatters={'Volume ID': _format_volume_id},
) for s in data))
class RestoreBackup(show.ShowOne):
"""Restore backup"""
log = logging.getLogger(__name__ + ".RestoreBackup")
def get_parser(self, prog_name):
parser = super(RestoreBackup, self).get_parser(prog_name)
parser.add_argument(
"backup",
metavar="<backup>",
help="Backup to restore (ID only)"
)
parser.add_argument(
"volume",
metavar="<volume>",
help="Volume to restore to (name or ID)"
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action: (%s)", parsed_args)
volume_client = self.app.client_manager.volume
backup = utils.find_resource(volume_client.backups, parsed_args.backup)
destination_volume = utils.find_resource(volume_client.volumes,
parsed_args.volume)
return volume_client.restores.restore(backup.id, destination_volume.id)
class ShowBackup(show.ShowOne): class ShowBackup(show.ShowOne):
"""Display backup details""" """Display backup details"""

@ -367,7 +367,10 @@ openstack.volume.v1 =
volume_type_unset = openstackclient.volume.v1.type:UnsetVolumeType volume_type_unset = openstackclient.volume.v1.type:UnsetVolumeType
openstack.volume.v2 = openstack.volume.v2 =
backup_create = openstackclient.volume.v2.backup:CreateBackup
backup_delete = openstackclient.volume.v2.backup:DeleteBackup backup_delete = openstackclient.volume.v2.backup:DeleteBackup
backup_list = openstackclient.volume.v2.backup:ListBackup
backup_restore = openstackclient.volume.v2.backup:RestoreBackup
backup_show = openstackclient.volume.v2.backup:ShowBackup backup_show = openstackclient.volume.v2.backup:ShowBackup
snapshot_delete = openstackclient.volume.v2.snapshot:DeleteSnapshot snapshot_delete = openstackclient.volume.v2.snapshot:DeleteSnapshot