Add support for volume v2 API

Added following commands for volume V2 API:
volume show
volume delete
volume type show
volume type delete
snapshot show
snapshot delete
backup show
backup delete

Implements: blueprint volume-v2
Change-Id: I68bd303c194f304ad15f899d335b72a8bf3ebe10
This commit is contained in:
Amey Bhide 2015-05-27 12:31:59 -07:00
parent 211c14c638
commit 5361652d8f
13 changed files with 771 additions and 1 deletions

View File

@ -0,0 +1,136 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import mock
from openstackclient.tests import fakes
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
from openstackclient.tests import utils
volume_id = "ce26708d-a7f8-4b4b-9861-4a80256615a6"
volume_name = "fake_volume"
volume_description = "fake description"
volume_status = "available"
volume_size = 20
volume_type = "fake_lvmdriver-1"
volume_metadata = {
"foo": "bar"
}
volume_snapshot_id = 1
volume_availability_zone = "nova"
volume_attachments = ["fake_attachments"]
VOLUME = {
"id": volume_id,
"name": volume_name,
"description": volume_description,
"status": volume_status,
"size": volume_size,
"volume_type": volume_type,
"metadata": volume_metadata,
"snapshot_id": volume_snapshot_id,
"availability_zone": volume_availability_zone,
"attachments": volume_attachments
}
VOLUME_columns = tuple(sorted(VOLUME))
VOLUME_data = tuple((VOLUME[x] for x in sorted(VOLUME)))
snapshot_id = "cb2d364e-4d1c-451a-8c68-b5bbcb340fb2"
snapshot_name = "fake_snapshot"
snapshot_description = "fake description"
snapshot_size = 10
snapshot_metadata = {
"foo": "bar"
}
snapshot_volume_id = "bdbae8dc-e6ca-43c0-8076-951cc1b093a4"
SNAPSHOT = {
"id": snapshot_id,
"name": snapshot_name,
"description": snapshot_description,
"size": snapshot_size,
"metadata": snapshot_metadata
}
SNAPSHOT_columns = tuple(sorted(SNAPSHOT))
SNAPSHOT_data = tuple((SNAPSHOT[x] for x in sorted(SNAPSHOT)))
type_id = "5520dc9e-6f9b-4378-a719-729911c0f407"
type_description = "fake description"
type_name = "fake-lvmdriver-1"
type_extra_specs = {
"foo": "bar"
}
TYPE = {
'id': type_id,
'name': type_name,
'description': type_description,
'extra_specs': type_extra_specs
}
TYPE_columns = tuple(sorted(TYPE))
TYPE_data = tuple((TYPE[x] for x in sorted(TYPE)))
backup_id = "3c409fe6-4d03-4a06-aeab-18bdcdf3c8f4"
backup_volume_id = "bdbae8dc-e6ca-43c0-8076-951cc1b093a4"
backup_name = "fake_backup"
backup_description = "fake description"
backup_object_count = None
backup_container = None
backup_size = 10
BACKUP = {
"id": backup_id,
"name": backup_name,
"volume_id": backup_volume_id,
"description": backup_description,
"object_count": backup_object_count,
"container": backup_container,
"size": backup_size
}
BACKUP_columns = tuple(sorted(BACKUP))
BACKUP_data = tuple((BACKUP[x] for x in sorted(BACKUP)))
class FakeVolumeClient(object):
def __init__(self, **kwargs):
self.volumes = mock.Mock()
self.volumes.resource_class = fakes.FakeResource(None, {})
self.volume_snapshots = mock.Mock()
self.volume_snapshots.resource_class = fakes.FakeResource(None, {})
self.backups = mock.Mock()
self.backups.resource_class = fakes.FakeResource(None, {})
self.volume_types = mock.Mock()
self.volume_types.resource_class = fakes.FakeResource(None, {})
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
class TestVolume(utils.TestCommand):
def setUp(self):
super(TestVolume, self).setUp()
self.app.client_manager.volume = FakeVolumeClient(
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN
)
self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client(
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN
)

View File

@ -0,0 +1,82 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import copy
from openstackclient.tests import fakes
from openstackclient.tests.volume.v2 import fakes as volume_fakes
from openstackclient.volume.v2 import backup
class TestBackup(volume_fakes.TestVolume):
def setUp(self):
super(TestBackup, self).setUp()
self.backups_mock = self.app.client_manager.volume.backups
self.backups_mock.reset_mock()
class TestBackupShow(TestBackup):
def setUp(self):
super(TestBackupShow, self).setUp()
self.backups_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.BACKUP),
loaded=True)
# Get the command object to test
self.cmd = backup.ShowBackup(self.app, None)
def test_backup_show(self):
arglist = [
volume_fakes.backup_id
]
verifylist = [
("backup", volume_fakes.backup_id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.backups_mock.get.assert_called_with(volume_fakes.backup_id)
self.assertEqual(volume_fakes.BACKUP_columns, columns)
self.assertEqual(volume_fakes.BACKUP_data, data)
class TestBackupDelete(TestBackup):
def setUp(self):
super(TestBackupDelete, self).setUp()
self.backups_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.BACKUP),
loaded=True)
self.backups_mock.delete.return_value = None
# Get the command object to mock
self.cmd = backup.DeleteBackup(self.app, None)
def test_backup_delete(self):
arglist = [
volume_fakes.backup_id
]
verifylist = [
("backups", [volume_fakes.backup_id])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.backups_mock.delete.assert_called_with(volume_fakes.backup_id)

View File

@ -0,0 +1,82 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import copy
from openstackclient.tests import fakes
from openstackclient.tests.volume.v2 import fakes as volume_fakes
from openstackclient.volume.v2 import snapshot
class TestSnapshot(volume_fakes.TestVolume):
def setUp(self):
super(TestSnapshot, self).setUp()
self.snapshots_mock = self.app.client_manager.volume.volume_snapshots
self.snapshots_mock.reset_mock()
class TestSnapshotShow(TestSnapshot):
def setUp(self):
super(TestSnapshotShow, self).setUp()
self.snapshots_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.SNAPSHOT),
loaded=True)
# Get the command object to test
self.cmd = snapshot.ShowSnapshot(self.app, None)
def test_snapshot_show(self):
arglist = [
volume_fakes.snapshot_id
]
verifylist = [
("snapshot", volume_fakes.snapshot_id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.snapshots_mock.get.assert_called_with(volume_fakes.snapshot_id)
self.assertEqual(volume_fakes.SNAPSHOT_columns, columns)
self.assertEqual(volume_fakes.SNAPSHOT_data, data)
class TestSnapshotDelete(TestSnapshot):
def setUp(self):
super(TestSnapshotDelete, self).setUp()
self.snapshots_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.SNAPSHOT),
loaded=True)
self.snapshots_mock.delete.return_value = None
# Get the command object to mock
self.cmd = snapshot.DeleteSnapshot(self.app, None)
def test_snapshot_delete(self):
arglist = [
volume_fakes.snapshot_id
]
verifylist = [
("snapshots", [volume_fakes.snapshot_id])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.snapshots_mock.delete.assert_called_with(volume_fakes.snapshot_id)

View File

@ -0,0 +1,82 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import copy
from openstackclient.tests import fakes
from openstackclient.tests.volume.v2 import fakes as volume_fakes
from openstackclient.volume.v2 import volume_type
class TestType(volume_fakes.TestVolume):
def setUp(self):
super(TestType, self).setUp()
self.types_mock = self.app.client_manager.volume.volume_types
self.types_mock.reset_mock()
class TestTypeShow(TestType):
def setUp(self):
super(TestTypeShow, self).setUp()
self.types_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.TYPE),
loaded=True)
# Get the command object to test
self.cmd = volume_type.ShowVolumeType(self.app, None)
def test_type_show(self):
arglist = [
volume_fakes.type_id
]
verifylist = [
("volume_type", volume_fakes.type_id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.types_mock.get.assert_called_with(volume_fakes.type_id)
self.assertEqual(volume_fakes.TYPE_columns, columns)
self.assertEqual(volume_fakes.TYPE_data, data)
class TestTypeDelete(TestType):
def setUp(self):
super(TestTypeDelete, self).setUp()
self.types_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.TYPE),
loaded=True)
self.types_mock.delete.return_value = None
# Get the command object to mock
self.cmd = volume_type.DeleteVolumeType(self.app, None)
def test_type_delete(self):
arglist = [
volume_fakes.type_id
]
verifylist = [
("volume_type", volume_fakes.type_id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.types_mock.delete.assert_called_with(volume_fakes.type_id)

View File

@ -0,0 +1,82 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import copy
from openstackclient.tests import fakes
from openstackclient.tests.volume.v2 import fakes as volume_fakes
from openstackclient.volume.v2 import volume
class TestVolume(volume_fakes.TestVolume):
def setUp(self):
super(TestVolume, self).setUp()
self.volumes_mock = self.app.client_manager.volume.volumes
self.volumes_mock.reset_mock()
class TestVolumeShow(TestVolume):
def setUp(self):
super(TestVolumeShow, self).setUp()
self.volumes_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.VOLUME),
loaded=True)
# Get the command object to test
self.cmd = volume.ShowVolume(self.app, None)
def test_volume_show(self):
arglist = [
volume_fakes.volume_id
]
verifylist = [
("volume", volume_fakes.volume_id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.volumes_mock.get.assert_called_with(volume_fakes.volume_id)
self.assertEqual(volume_fakes.VOLUME_columns, columns)
self.assertEqual(volume_fakes.VOLUME_data, data)
class TestVolumeDelete(TestVolume):
def setUp(self):
super(TestVolumeDelete, self).setUp()
self.volumes_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(volume_fakes.VOLUME),
loaded=True)
self.volumes_mock.delete.return_value = None
# Get the command object to mock
self.cmd = volume.DeleteVolume(self.app, None)
def test_volume_delete(self):
arglist = [
volume_fakes.volume_id
]
verifylist = [
("volumes", [volume_fakes.volume_id])
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.volumes_mock.delete.assert_called_with(volume_fakes.volume_id)

View File

@ -23,7 +23,8 @@ DEFAULT_VOLUME_API_VERSION = '1'
API_VERSION_OPTION = 'os_volume_api_version'
API_NAME = "volume"
API_VERSIONS = {
"1": "cinderclient.v1.client.Client"
"1": "cinderclient.v1.client.Client",
"2": "cinderclient.v2.client.Client"
}

View File

View File

@ -0,0 +1,70 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Volume v2 Backup action implementations"""
import logging
from cliff import command
from cliff import show
import six
from openstackclient.common import utils
class DeleteBackup(command.Command):
"""Delete backup(s)"""
log = logging.getLogger(__name__ + ".DeleteBackup")
def get_parser(self, prog_name):
parser = super(DeleteBackup, self).get_parser(prog_name)
parser.add_argument(
"backups",
metavar="<backup>",
nargs="+",
help="Backup(s) to delete (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
for backup in parsed_args.backups:
backup_id = utils.find_resource(
volume_client.backups, backup).id
volume_client.backups.delete(backup_id)
return
class ShowBackup(show.ShowOne):
"""Display backup details"""
log = logging.getLogger(__name__ + ".ShowBackup")
def get_parser(self, prog_name):
parser = super(ShowBackup, self).get_parser(prog_name)
parser.add_argument(
"backup",
metavar="<backup>",
help="Backup to display (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)
backup._info.pop("links", None)
return zip(*sorted(six.iteritems(backup._info)))

View File

@ -0,0 +1,71 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Volume v2 snapshot action implementations"""
import logging
from cliff import command
from cliff import show
import six
from openstackclient.common import utils
class DeleteSnapshot(command.Command):
"""Delete volume snapshot(s)"""
log = logging.getLogger(__name__ + ".DeleteSnapshot")
def get_parser(self, prog_name):
parser = super(DeleteSnapshot, self).get_parser(prog_name)
parser.add_argument(
"snapshots",
metavar="<snapshot>",
nargs="+",
help="Snapsho(s) to delete (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
for snapshot in parsed_args.snapshots:
snapshot_id = utils.find_resource(
volume_client.volume_snapshots, snapshot).id
volume_client.volume_snapshots.delete(snapshot_id)
return
class ShowSnapshot(show.ShowOne):
"""Display snapshot details"""
log = logging.getLogger(__name__ + ".ShowSnapshot")
def get_parser(self, prog_name):
parser = super(ShowSnapshot, self).get_parser(prog_name)
parser.add_argument(
"snapshot",
metavar="<snapshot>",
help="Snapshot to display (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
snapshot = utils.find_resource(
volume_client.volume_snapshots, parsed_args.snapshot)
snapshot = volume_client.volume_snapshots.get(snapshot.id)
return zip(*sorted(six.iteritems(snapshot._info)))

View File

@ -0,0 +1,83 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Volume V2 Volume action implementations"""
import logging
from cliff import command
from cliff import show
import six
from openstackclient.common import utils
class DeleteVolume(command.Command):
"""Delete volume(s)"""
log = logging.getLogger(__name__ + ".DeleteVolume")
def get_parser(self, prog_name):
parser = super(DeleteVolume, self).get_parser(prog_name)
parser.add_argument(
"volumes",
metavar="<volume>",
nargs="+",
help="Volume(s) to delete (name or ID)"
)
parser.add_argument(
"--force",
dest="force",
action="store_true",
default=False,
help="Attempt forced removal of volume(s), regardless of state "
"(defaults to False"
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action: (%s)", parsed_args)
volume_client = self.app.client_manager.volume
for volume in parsed_args.volumes:
volume_obj = utils.find_resource(
volume_client.volumes, volume)
if parsed_args.force:
volume_client.volumes.force_delete(volume_obj.id)
else:
volume_client.volumes.delete(volume_obj.id)
return
class ShowVolume(show.ShowOne):
"""Display volume details"""
log = logging.getLogger(__name__ + '.ShowVolume')
def get_parser(self, prog_name):
parser = super(ShowVolume, self).get_parser(prog_name)
parser.add_argument(
'volume',
metavar="<volume-id>",
help="Volume to display (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
volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
# Remove key links from being displayed
volume._info.pop("links", None)
return zip(*sorted(six.iteritems(volume._info)))

View File

@ -0,0 +1,68 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Volume v2 Type action implementations"""
import logging
from cliff import command
from cliff import show
import six
from openstackclient.common import utils
class DeleteVolumeType(command.Command):
"""Delete volume type"""
log = logging.getLogger(__name__ + ".DeleteVolumeType")
def get_parser(self, prog_name):
parser = super(DeleteVolumeType, self).get_parser(prog_name)
parser.add_argument(
"volume_type",
metavar="<volume-type>",
help="Volume type to delete (name or ID)"
)
return parser
def take_action(self, parsed_args):
self.log.info("take_action: (%s)", parsed_args)
volume_client = self.app.client_manager.volume
volume_type = utils.find_resource(
volume_client.volume_types, parsed_args.volume_type)
volume_client.volume_types.delete(volume_type.id)
return
class ShowVolumeType(show.ShowOne):
"""Display volume type details"""
log = logging.getLogger(__name__ + ".ShowVolumeType")
def get_parser(self, prog_name):
parser = super(ShowVolumeType, self).get_parser(prog_name)
parser.add_argument(
"volume_type",
metavar="<volume-type>",
help="Volume type to display (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
volume_type = utils.find_resource(
volume_client.volume_types, parsed_args.volume_type)
return zip(*sorted(six.iteritems(volume_type._info)))

View File

@ -361,6 +361,19 @@ openstack.volume.v1 =
volume_type_set = openstackclient.volume.v1.type:SetVolumeType
volume_type_unset = openstackclient.volume.v1.type:UnsetVolumeType
openstack.volume.v2 =
backup_delete = openstackclient.volume.v2.backup:DeleteBackup
backup_show = openstackclient.volume.v2.backup:ShowBackup
snapshot_delete = openstackclient.volume.v2.snapshot:DeleteSnapshot
snapshot_show = openstackclient.volume.v2.snapshot:ShowSnapshot
volume_delete = openstackclient.volume.v2.volume:DeleteVolume
volume_show = openstackclient.volume.v2.volume:ShowVolume
volume_type_delete = openstackclient.volume.v2.volume_type:DeleteVolumeType
volume_type_show = openstackclient.volume.v2.volume_type:ShowVolumeType
[build_sphinx]
source-dir = doc/source
build-dir = doc/build