Add list method and query support for cinder volume and snapshot

This patch do those things:
  1. Add list method for volume and snapshot in _proxy.py
  2. Change the Volume(block_store/v2/volume.Volume)'s base class
  to resource2 to support QueryParameters.
  3. Change the Snapshot(block_store/v2/snapshot.Snapshot)'s base
  class to resource2 to support QueryParameters.
  4. Change the type(block_store/v2/type/type.Type)'s base class
  to resource2.
  5. Add query support for volume and snapshot with parameters:
  all_tenants, name, status, volume_id(snapshot only).

Change-Id: I60f537ea8fd67283fd84addb334bdd932f537635
This commit is contained in:
tianmaofu 2016-10-24 10:22:09 +00:00
parent b2a0d5c6a5
commit cb69d09f4a
8 changed files with 165 additions and 66 deletions

View File

@ -13,10 +13,10 @@
from openstack.block_store.v2 import snapshot as _snapshot
from openstack.block_store.v2 import type as _type
from openstack.block_store.v2 import volume as _volume
from openstack import proxy
from openstack import proxy2
class Proxy(proxy.BaseProxy):
class Proxy(proxy2.BaseProxy):
def get_snapshot(self, snapshot):
"""Get a single snapshot
@ -31,6 +31,28 @@ class Proxy(proxy.BaseProxy):
"""
return self._get(_snapshot.Snapshot, snapshot)
def snapshots(self, details=True, **query):
"""Retrieve a generator of snapshots
:param bool details: When set to ``False``
:class:`~openstack.block_store.v2.snapshot.Snapshot`
objects will be returned. The default, ``True``, will cause
:class:`~openstack.block_store.v2.snapshot.SnapshotDetail`
objects to be returned.
:param kwargs \*\*query: Optional query parameters to be sent to limit
the snapshots being returned. Available parameters include:
* name: Name of the snapshot as a string.
* all_tenants: Whether return the snapshots of all tenants.
* volume_id: volume id of a snapshot.
* status: Value of the status of the snapshot so that you can
filter on "available" for example.
:returns: A generator of snapshot objects.
"""
snapshot = _snapshot.SnapshotDetail if details else _snapshot.Snapshot
return self._list(snapshot, paginated=True, **query)
def create_snapshot(self, **attrs):
"""Create a new snapshot from attributes
@ -72,6 +94,13 @@ class Proxy(proxy.BaseProxy):
"""
return self._get(_type.Type, type)
def types(self):
"""Retrieve a generator of volume types
:returns: A generator of volume type objects.
"""
return self._list(_type.Type, paginated=False)
def create_type(self, **attrs):
"""Create a new type from attributes
@ -111,6 +140,27 @@ class Proxy(proxy.BaseProxy):
"""
return self._get(_volume.Volume, volume)
def volumes(self, details=True, **query):
"""Retrieve a generator of volumes
:param bool details: When set to ``False``
:class:`~openstack.block_store.v2.volume.Volume` objects
will be returned. The default, ``True``, will cause
:class:`~openstack.block_store.v2.volume.VolumeDetail`
objects to be returned.
:param kwargs \*\*query: Optional query parameters to be sent to limit
the volumes being returned. Available parameters include:
* name: Name of the volume as a string.
* all_tenants: Whether return the volumes of all tenants
* status: Value of the status of the volume so that you can filter
on "available" for example.
:returns: A generator of volume objects.
"""
volume = _volume.VolumeDetail if details else _volume.Volume
return self._list(volume, paginated=True, **query)
def create_volume(self, **attrs):
"""Create a new volume from attributes

View File

@ -12,43 +12,47 @@
from openstack.block_store import block_store_service
from openstack import format
from openstack import resource
from openstack import resource2
class Snapshot(resource.Resource):
class Snapshot(resource2.Resource):
resource_key = "snapshot"
resources_key = "snapshots"
base_path = "/snapshots"
service = block_store_service.BlockStoreService()
_query_mapping = resource2.QueryParameters('all_tenants', 'name', 'status',
'volume_id')
# capabilities
allow_retrieve = True
allow_get = True
allow_create = True
allow_delete = True
allow_update = True
allow_list = True
# Properties
#: A ID representing this snapshot.
id = resource.prop("id")
id = resource2.Body("id")
#: Name of the snapshot. Default is None.
name = resource.prop("name")
name = resource2.Body("name")
#: The current status of this snapshot. Potential values are creating,
#: available, deleting, error, and error_deleting.
status = resource.prop("status")
status = resource2.Body("status")
#: Description of snapshot. Default is None.
description = resource.prop("description")
description = resource2.Body("description")
#: The timestamp of this snapshot creation.
created_at = resource.prop("created_at")
created_at = resource2.Body("created_at")
#: Metadata associated with this snapshot.
metadata = resource.prop("metadata", type=dict)
metadata = resource2.Body("metadata", type=dict)
#: The ID of the volume this snapshot was taken of.
volume_id = resource.prop("volume_id")
volume_id = resource2.Body("volume_id")
#: The size of the volume, in GBs.
size = resource.prop("size", type=int)
size = resource2.Body("size", type=int)
#: Indicate whether to create snapshot, even if the volume is attached.
#: Default is ``False``. *Type: bool*
is_forced = resource.prop("force", type=format.BoolStr)
is_forced = resource2.Body("force", type=format.BoolStr)
class SnapshotDetail(Snapshot):
@ -56,6 +60,6 @@ class SnapshotDetail(Snapshot):
base_path = "/snapshots/detail"
#: The percentage of completeness the snapshot is currently at.
progress = resource.prop("os-extended-snapshot-attributes:progress")
progress = resource2.Body("os-extended-snapshot-attributes:progress")
#: The project ID this snapshot is associated with.
project_id = resource.prop("os-extended-snapshot-attributes:project_id")
project_id = resource2.Body("os-extended-snapshot-attributes:project_id")

View File

@ -11,24 +11,25 @@
# under the License.
from openstack.block_store import block_store_service
from openstack import resource
from openstack import resource2
class Type(resource.Resource):
class Type(resource2.Resource):
resource_key = "volume_type"
resources_key = "volume_types"
base_path = "/types"
service = block_store_service.BlockStoreService()
# capabilities
allow_retrieve = True
allow_get = True
allow_create = True
allow_delete = True
allow_list = True
# Properties
#: A ID representing this type.
id = resource.prop("id")
id = resource2.Body("id")
#: Name of the type.
name = resource.prop("name")
name = resource2.Body("name")
#: A dict of extra specifications. "capabilities" is a usual key.
extra_specs = resource.prop("extra_specs", type=dict)
extra_specs = resource2.Body("extra_specs", type=dict)

View File

@ -12,63 +12,66 @@
from openstack.block_store import block_store_service
from openstack import format
from openstack import resource
from openstack import resource2
class Volume(resource.Resource):
class Volume(resource2.Resource):
resource_key = "volume"
resources_key = "volumes"
base_path = "/volumes"
service = block_store_service.BlockStoreService()
_query_mapping = resource2.QueryParameters('all_tenants', 'name', 'status')
# capabilities
allow_retrieve = True
allow_get = True
allow_create = True
allow_delete = True
allow_update = True
allow_list = True
# Properties
#: A ID representing this volume.
id = resource.prop("id")
id = resource2.Body("id")
#: The name of this volume.
name = resource.prop("name")
name = resource2.Body("name")
#: A list of links associated with this volume. *Type: list*
links = resource.prop("links", type=list)
links = resource2.Body("links", type=list)
#: The availability zone.
availability_zone = resource.prop("availability_zone")
availability_zone = resource2.Body("availability_zone")
#: To create a volume from an existing volume, specify the ID of
#: the existing volume. If specified, the volume is created with
#: same size of the source volume.
source_volume_id = resource.prop("source_volid")
source_volume_id = resource2.Body("source_volid")
#: The volume description.
description = resource.prop("description")
description = resource2.Body("description")
#: To create a volume from an existing snapshot, specify the ID of
#: the existing volume snapshot. If specified, the volume is created
#: in same availability zone and with same size of the snapshot.
snapshot_id = resource.prop("snapshot_id")
snapshot_id = resource2.Body("snapshot_id")
#: The size of the volume, in GBs. *Type: int*
size = resource.prop("size", type=int)
size = resource2.Body("size", type=int)
#: The ID of the image from which you want to create the volume.
#: Required to create a bootable volume.
image_id = resource.prop("imageRef")
image_id = resource2.Body("imageRef")
#: The name of the associated volume type.
volume_type = resource.prop("volume_type")
volume_type = resource2.Body("volume_type")
#: Enables or disables the bootable attribute. You can boot an
#: instance from a bootable volume. *Type: bool*
is_bootable = resource.prop("bootable", type=format.BoolStr)
is_bootable = resource2.Body("bootable", type=format.BoolStr)
#: One or more metadata key and value pairs to associate with the volume.
metadata = resource.prop("metadata")
metadata = resource2.Body("metadata")
#: One of the following values: creating, available, attaching, in-use
#: deleting, error, error_deleting, backing-up, restoring-backup,
#: error_restoring. For details on these statuses, see the
#: Block Storage API documentation.
status = resource.prop("status")
status = resource2.Body("status")
#: TODO(briancurtin): This is currently undocumented in the API.
attachments = resource.prop("attachments")
attachments = resource2.Body("attachments")
#: The timestamp of this volume creation.
created_at = resource.prop("created_at")
created_at = resource2.Body("created_at")
class VolumeDetail(Volume):
@ -76,24 +79,24 @@ class VolumeDetail(Volume):
base_path = "/volumes/detail"
#: The volume's current back-end.
host = resource.prop("os-vol-host-attr:host")
host = resource2.Body("os-vol-host-attr:host")
#: The project ID associated with current back-end.
project_id = resource.prop("os-vol-tenant-attr:tenant_id")
project_id = resource2.Body("os-vol-tenant-attr:tenant_id")
#: The status of this volume's migration (None means that a migration
#: is not currently in progress).
migration_status = resource.prop("os-vol-mig-status-attr:migstat")
migration_status = resource2.Body("os-vol-mig-status-attr:migstat")
#: The volume ID that this volume's name on the back-end is based on.
migration_id = resource.prop("os-vol-mig-status-attr:name_id")
migration_id = resource2.Body("os-vol-mig-status-attr:name_id")
#: Status of replication on this volume.
replication_status = resource.prop("replication_status")
replication_status = resource2.Body("replication_status")
#: Extended replication status on this volume.
extended_replication_status = resource.prop(
extended_replication_status = resource2.Body(
"os-volume-replication:extended_status")
#: ID of the consistency group.
consistency_group_id = resource.prop("consistencygroup_id")
consistency_group_id = resource2.Body("consistencygroup_id")
#: Data set by the replication driver
replication_driver_data = resource.prop(
replication_driver_data = resource2.Body(
"os-volume-replication:driver_data")
#: ``True`` if this volume is encrypted, ``False`` if not.
#: *Type: bool*
is_encrypted = resource.prop("encrypted", type=format.BoolStr)
is_encrypted = resource2.Body("encrypted", type=format.BoolStr)

View File

@ -14,10 +14,10 @@ from openstack.block_store.v2 import _proxy
from openstack.block_store.v2 import snapshot
from openstack.block_store.v2 import type
from openstack.block_store.v2 import volume
from openstack.tests.unit import test_proxy_base
from openstack.tests.unit import test_proxy_base2
class TestVolumeProxy(test_proxy_base.TestProxyBase):
class TestVolumeProxy(test_proxy_base2.TestProxyBase):
def setUp(self):
super(TestVolumeProxy, self).setUp()
self.proxy = _proxy.Proxy(self.session)
@ -25,6 +25,18 @@ class TestVolumeProxy(test_proxy_base.TestProxyBase):
def test_snapshot_get(self):
self.verify_get(self.proxy.get_snapshot, snapshot.Snapshot)
def test_snapshots_detailed(self):
self.verify_list(self.proxy.snapshots, snapshot.SnapshotDetail,
paginated=True,
method_kwargs={"details": True, "query": 1},
expected_kwargs={"query": 1})
def test_snapshots_not_detailed(self):
self.verify_list(self.proxy.snapshots, snapshot.Snapshot,
paginated=True,
method_kwargs={"details": False, "query": 1},
expected_kwargs={"query": 1})
def test_snapshot_create_attrs(self):
self.verify_create(self.proxy.create_snapshot, snapshot.Snapshot)
@ -39,6 +51,9 @@ class TestVolumeProxy(test_proxy_base.TestProxyBase):
def test_type_get(self):
self.verify_get(self.proxy.get_type, type.Type)
def test_types(self):
self.verify_list(self.proxy.types, type.Type, paginated=False)
def test_type_create_attrs(self):
self.verify_create(self.proxy.create_type, type.Type)
@ -51,6 +66,18 @@ class TestVolumeProxy(test_proxy_base.TestProxyBase):
def test_volume_get(self):
self.verify_get(self.proxy.get_volume, volume.Volume)
def test_volumes_detailed(self):
self.verify_list(self.proxy.volumes, volume.VolumeDetail,
paginated=True,
method_kwargs={"details": True, "query": 1},
expected_kwargs={"query": 1})
def test_volumes_not_detailed(self):
self.verify_list(self.proxy.volumes, volume.Volume,
paginated=True,
method_kwargs={"details": False, "query": 1},
expected_kwargs={"query": 1})
def test_volume_create_attrs(self):
self.verify_create(self.proxy.create_volume, volume.Volume)

View File

@ -44,17 +44,24 @@ class TestSnapshot(testtools.TestCase):
sot = snapshot.Snapshot(SNAPSHOT)
self.assertEqual("snapshot", sot.resource_key)
self.assertEqual("snapshots", sot.resources_key)
self.assertEqual("id", sot.id_attribute)
self.assertEqual("/snapshots", sot.base_path)
self.assertEqual("volume", sot.service.service_type)
self.assertTrue(sot.allow_get)
self.assertTrue(sot.allow_update)
self.assertTrue(sot.allow_create)
self.assertTrue(sot.allow_retrieve)
self.assertTrue(sot.allow_delete)
self.assertFalse(sot.allow_list)
self.assertTrue(sot.allow_list)
self.assertDictEqual({"name": "name",
"status": "status",
"all_tenants": "all_tenants",
"volume_id": "volume_id",
"limit": "limit",
"marker": "marker"},
sot._query_mapping._mapping)
def test_create_basic(self):
sot = snapshot.Snapshot(SNAPSHOT)
sot = snapshot.Snapshot(**SNAPSHOT)
self.assertEqual(SNAPSHOT["id"], sot.id)
self.assertEqual(SNAPSHOT["status"], sot.status)
self.assertEqual(SNAPSHOT["created_at"], sot.created_at)
@ -73,7 +80,7 @@ class TestSnapshotDetail(testtools.TestCase):
self.assertEqual("/snapshots/detail", sot.base_path)
def test_create_detailed(self):
sot = snapshot.SnapshotDetail(DETAILED_SNAPSHOT)
sot = snapshot.SnapshotDetail(**DETAILED_SNAPSHOT)
self.assertEqual(
DETAILED_SNAPSHOT["os-extended-snapshot-attributes:progress"],

View File

@ -27,23 +27,23 @@ TYPE = {
class TestType(testtools.TestCase):
def test_basic(self):
sot = type.Type(TYPE)
sot = type.Type(**TYPE)
self.assertEqual("volume_type", sot.resource_key)
self.assertEqual("volume_types", sot.resources_key)
self.assertEqual("id", sot.id_attribute)
self.assertEqual("/types", sot.base_path)
self.assertEqual("volume", sot.service.service_type)
self.assertTrue(sot.allow_create)
self.assertTrue(sot.allow_retrieve)
self.assertTrue(sot.allow_get)
self.assertTrue(sot.allow_delete)
self.assertFalse(sot.allow_list)
self.assertTrue(sot.allow_list)
self.assertFalse(sot.allow_update)
def test_new(self):
sot = type.Type.new(id=FAKE_ID)
self.assertEqual(FAKE_ID, sot.id)
def test_create(self):
sot = type.Type(TYPE)
sot = type.Type(**TYPE)
self.assertEqual(TYPE["id"], sot.id)
self.assertEqual(TYPE["extra_specs"], sot.extra_specs)
self.assertEqual(TYPE["name"], sot.name)

View File

@ -58,16 +58,23 @@ class TestVolume(testtools.TestCase):
sot = volume.Volume(VOLUME)
self.assertEqual("volume", sot.resource_key)
self.assertEqual("volumes", sot.resources_key)
self.assertEqual("id", sot.id_attribute)
self.assertEqual("/volumes", sot.base_path)
self.assertEqual("volume", sot.service.service_type)
self.assertTrue(sot.allow_get)
self.assertTrue(sot.allow_create)
self.assertTrue(sot.allow_retrieve)
self.assertTrue(sot.allow_update)
self.assertTrue(sot.allow_delete)
self.assertFalse(sot.allow_list)
self.assertTrue(sot.allow_list)
self.assertDictEqual({"name": "name",
"status": "status",
"all_tenants": "all_tenants",
"limit": "limit",
"marker": "marker"},
sot._query_mapping._mapping)
def test_create(self):
sot = volume.Volume(VOLUME)
sot = volume.Volume(**VOLUME)
self.assertEqual(VOLUME["id"], sot.id)
self.assertEqual(VOLUME["status"], sot.status)
self.assertEqual(VOLUME["attachments"], sot.attachments)
@ -91,7 +98,7 @@ class TestVolumeDetail(testtools.TestCase):
self.assertEqual("/volumes/detail", sot.base_path)
def test_create(self):
sot = volume.VolumeDetail(VOLUME_DETAIL)
sot = volume.VolumeDetail(**VOLUME_DETAIL)
self.assertEqual(VOLUME_DETAIL["os-vol-host-attr:host"], sot.host)
self.assertEqual(VOLUME_DETAIL["os-vol-tenant-attr:tenant_id"],
sot.project_id)