Support block_store types where IDs are taken
In an effort to widen support for resource properties that are IDs but can take the appropriate resource type for that ID, the block_store service throws a kink into it. The Volume type can contain a Snapshot, and a Snapshot can contain a Volume. Because of that loop, we can't do the usual `foo = resource.prop(foo, FooType)`. We can sort of emulate the behavior users will get out of it in the proxy by doing a type conversion in the create_volume and create_snapshot methods to support resource instances being passed in there. Additionally, Volume can be created from another Volume, but we can't have a prop refer to its own class, so that problem is solved the same as above. Lastly, the Volume resource has two props that can take real types: image and type. Change-Id: If8a5b24b05a341ae6bfe9dca6b93429dcfc8f408
This commit is contained in:
@@ -18,6 +18,23 @@ from openstack import proxy
|
|||||||
|
|
||||||
class Proxy(proxy.BaseProxy):
|
class Proxy(proxy.BaseProxy):
|
||||||
|
|
||||||
|
def _convert_id(self, attrs, value, resource_type):
|
||||||
|
"""Convert potential Resource values into IDs
|
||||||
|
|
||||||
|
The structure of Snapshot and Volume resources is such that their
|
||||||
|
Resource subclasses contain properties of each other's types, because
|
||||||
|
a Snapshot can be created of a Volume, and a Volume can be created from
|
||||||
|
a Snapshot. Additionally, a Volume can be created from another
|
||||||
|
Volume, yet the source_volume prop can't refer to the current class.
|
||||||
|
We work around this by simply looking for those Resource types
|
||||||
|
before sending them on.
|
||||||
|
"""
|
||||||
|
val = attrs.pop(value, None)
|
||||||
|
if val is not None:
|
||||||
|
if isinstance(val, resource_type):
|
||||||
|
val = val.id
|
||||||
|
attrs[value] = val
|
||||||
|
|
||||||
def get_snapshot(self, snapshot):
|
def get_snapshot(self, snapshot):
|
||||||
"""Get a single snapshot
|
"""Get a single snapshot
|
||||||
|
|
||||||
@@ -41,6 +58,7 @@ class Proxy(proxy.BaseProxy):
|
|||||||
:returns: The results of snapshot creation
|
:returns: The results of snapshot creation
|
||||||
:rtype: :class:`~openstack.volume.v2.snapshot.Snapshot`
|
:rtype: :class:`~openstack.volume.v2.snapshot.Snapshot`
|
||||||
"""
|
"""
|
||||||
|
self._convert_id(attrs, "volume", _volume.Volume)
|
||||||
return self._create(_snapshot.Snapshot, **attrs)
|
return self._create(_snapshot.Snapshot, **attrs)
|
||||||
|
|
||||||
def delete_snapshot(self, snapshot, ignore_missing=True):
|
def delete_snapshot(self, snapshot, ignore_missing=True):
|
||||||
@@ -121,6 +139,8 @@ class Proxy(proxy.BaseProxy):
|
|||||||
:returns: The results of volume creation
|
:returns: The results of volume creation
|
||||||
:rtype: :class:`~openstack.volume.v2.volume.Volume`
|
:rtype: :class:`~openstack.volume.v2.volume.Volume`
|
||||||
"""
|
"""
|
||||||
|
self._convert_id(attrs, "source_volume", _volume.Volume)
|
||||||
|
self._convert_id(attrs, "snapshot", _snapshot.Snapshot)
|
||||||
return self._create(_volume.Volume, **attrs)
|
return self._create(_volume.Volume, **attrs)
|
||||||
|
|
||||||
def delete_volume(self, volume, ignore_missing=True):
|
def delete_volume(self, volume, ignore_missing=True):
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from openstack.block_store import block_store_service
|
from openstack.block_store import block_store_service
|
||||||
|
from openstack.block_store.v2 import type as _type
|
||||||
|
from openstack.image.v2 import image as _image
|
||||||
from openstack import resource
|
from openstack import resource
|
||||||
|
|
||||||
|
|
||||||
@@ -50,9 +52,9 @@ class Volume(resource.Resource):
|
|||||||
size = resource.prop("size", type=int)
|
size = resource.prop("size", type=int)
|
||||||
#: The ID of the image from which you want to create the volume.
|
#: The ID of the image from which you want to create the volume.
|
||||||
#: Required to create a bootable volume.
|
#: Required to create a bootable volume.
|
||||||
image = resource.prop("imageRef")
|
image = resource.prop("imageRef", type=_image.Image)
|
||||||
#: The associated volume type.
|
#: The associated volume type.
|
||||||
type = resource.prop("volume_type")
|
type = resource.prop("volume_type", type=_type.Type)
|
||||||
#: Enables or disables the bootable attribute. You can boot an
|
#: Enables or disables the bootable attribute. You can boot an
|
||||||
#: instance from a bootable volume. *Type: bool*
|
#: instance from a bootable volume. *Type: bool*
|
||||||
bootable = resource.prop("bootable", type=bool)
|
bootable = resource.prop("bootable", type=bool)
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
from openstack.block_store.v2 import _proxy
|
from openstack.block_store.v2 import _proxy
|
||||||
from openstack.block_store.v2 import snapshot
|
from openstack.block_store.v2 import snapshot
|
||||||
from openstack.block_store.v2 import type
|
from openstack.block_store.v2 import type
|
||||||
@@ -28,6 +30,16 @@ class TestVolumeProxy(test_proxy_base.TestProxyBase):
|
|||||||
def test_snapshot_create_attrs(self):
|
def test_snapshot_create_attrs(self):
|
||||||
self.verify_create(self.proxy.create_snapshot, snapshot.Snapshot)
|
self.verify_create(self.proxy.create_snapshot, snapshot.Snapshot)
|
||||||
|
|
||||||
|
def test_snapshot_create_volume_prop(self):
|
||||||
|
self.proxy._create = mock.Mock()
|
||||||
|
|
||||||
|
id = "12345"
|
||||||
|
vol = volume.Volume({"id": id})
|
||||||
|
self.proxy.create_snapshot(volume=vol)
|
||||||
|
|
||||||
|
kwargs = self.proxy._create.call_args[1]
|
||||||
|
self.assertEqual({"volume": id}, kwargs)
|
||||||
|
|
||||||
def test_snapshot_delete(self):
|
def test_snapshot_delete(self):
|
||||||
self.verify_delete(self.proxy.delete_snapshot,
|
self.verify_delete(self.proxy.delete_snapshot,
|
||||||
snapshot.Snapshot, False)
|
snapshot.Snapshot, False)
|
||||||
@@ -54,6 +66,18 @@ class TestVolumeProxy(test_proxy_base.TestProxyBase):
|
|||||||
def test_volume_create_attrs(self):
|
def test_volume_create_attrs(self):
|
||||||
self.verify_create(self.proxy.create_volume, volume.Volume)
|
self.verify_create(self.proxy.create_volume, volume.Volume)
|
||||||
|
|
||||||
|
def test_volume_create_snapshot_prop(self):
|
||||||
|
self.proxy._create = mock.Mock()
|
||||||
|
|
||||||
|
id1 = "12345"
|
||||||
|
id2 = "67890"
|
||||||
|
snap = snapshot.Snapshot({"id": id1})
|
||||||
|
source = volume.Volume({"id": id2})
|
||||||
|
self.proxy.create_volume(snapshot=snap, source_volume=source)
|
||||||
|
|
||||||
|
kwargs = self.proxy._create.call_args[1]
|
||||||
|
self.assertEqual({"snapshot": id1, "source_volume": id2}, kwargs)
|
||||||
|
|
||||||
def test_volume_delete(self):
|
def test_volume_delete(self):
|
||||||
self.verify_delete(self.proxy.delete_volume, volume.Volume, False)
|
self.verify_delete(self.proxy.delete_volume, volume.Volume, False)
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import copy
|
|||||||
|
|
||||||
import testtools
|
import testtools
|
||||||
|
|
||||||
|
from openstack.block_store.v2 import type
|
||||||
from openstack.block_store.v2 import volume
|
from openstack.block_store.v2 import volume
|
||||||
|
from openstack.image.v2 import image
|
||||||
|
|
||||||
FAKE_ID = "6685584b-1eac-4da6-b5c3-555430cf68ff"
|
FAKE_ID = "6685584b-1eac-4da6-b5c3-555430cf68ff"
|
||||||
|
|
||||||
@@ -26,9 +28,10 @@ VOLUME = {
|
|||||||
"bootable": False,
|
"bootable": False,
|
||||||
"created_at": "2014-02-21T19:52:04.949734",
|
"created_at": "2014-02-21T19:52:04.949734",
|
||||||
"description": "something",
|
"description": "something",
|
||||||
"volume_type": None,
|
"volume_type": "some_type",
|
||||||
"snapshot_id": "93c2e2aa-7744-4fd6-a31a-80c4726b08d7",
|
"snapshot_id": "93c2e2aa-7744-4fd6-a31a-80c4726b08d7",
|
||||||
"source_volid": None,
|
"source_volid": None,
|
||||||
|
"image": "some_image",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"id": FAKE_ID,
|
"id": FAKE_ID,
|
||||||
"size": 10
|
"size": 10
|
||||||
@@ -74,11 +77,12 @@ class TestVolume(testtools.TestCase):
|
|||||||
self.assertEqual(VOLUME["bootable"], sot.bootable)
|
self.assertEqual(VOLUME["bootable"], sot.bootable)
|
||||||
self.assertEqual(VOLUME["created_at"], sot.created)
|
self.assertEqual(VOLUME["created_at"], sot.created)
|
||||||
self.assertEqual(VOLUME["description"], sot.description)
|
self.assertEqual(VOLUME["description"], sot.description)
|
||||||
self.assertEqual(VOLUME["volume_type"], sot.type)
|
self.assertEqual(type.Type({"id": VOLUME["volume_type"]}), sot.type)
|
||||||
self.assertEqual(VOLUME["snapshot_id"], sot.snapshot)
|
self.assertEqual(VOLUME["snapshot_id"], sot.snapshot)
|
||||||
self.assertEqual(VOLUME["source_volid"], sot.source_volume)
|
self.assertEqual(VOLUME["source_volid"], sot.source_volume)
|
||||||
self.assertEqual(VOLUME["metadata"], sot.metadata)
|
self.assertEqual(VOLUME["metadata"], sot.metadata)
|
||||||
self.assertEqual(VOLUME["size"], sot.size)
|
self.assertEqual(VOLUME["size"], sot.size)
|
||||||
|
self.assertEqual(image.Image({"id": VOLUME["image"]}), sot.image)
|
||||||
|
|
||||||
|
|
||||||
class TestVolumeDetail(testtools.TestCase):
|
class TestVolumeDetail(testtools.TestCase):
|
||||||
|
|||||||
Reference in New Issue
Block a user