Add support for updating Block Storage Volume type extra_spec attributes
While the volume_types/ endpoint returns extra_specs and accepts it on creation it doesn't support updating them through volume_types. It has to be updated through the volume_types/extra_specs endpoint. Change-Id: I5f9d5bcb102c5fd9fe60eb03e218c66f9c49592c
This commit is contained in:
@@ -175,6 +175,36 @@ class Proxy(_base_proxy.BaseBlockStorageProxy):
|
|||||||
"""
|
"""
|
||||||
return self._update(_type.Type, type, **attrs)
|
return self._update(_type.Type, type, **attrs)
|
||||||
|
|
||||||
|
def update_type_extra_specs(self, type, **attrs):
|
||||||
|
"""Update the extra_specs for a type
|
||||||
|
|
||||||
|
:param type: The value can be either the ID of a type or a
|
||||||
|
:class:`~openstack.volume.v3.type.Type` instance.
|
||||||
|
:param dict attrs: The extra_spec attributes to update on the
|
||||||
|
type represented by ``value``.
|
||||||
|
|
||||||
|
:returns: A dict containing updated extra_specs
|
||||||
|
|
||||||
|
"""
|
||||||
|
res = self._get_resource(_type.Type, type)
|
||||||
|
extra_specs = res.set_extra_specs(self, **attrs)
|
||||||
|
result = _type.Type.existing(id=res.id, extra_specs=extra_specs)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def delete_type_extra_specs(self, type, keys):
|
||||||
|
"""Delete the extra_specs for a type
|
||||||
|
|
||||||
|
Note: This method will do a HTTP DELETE request for every key in keys.
|
||||||
|
|
||||||
|
:param type: The value can be either the ID of a type or a
|
||||||
|
:class:`~openstack.volume.v3.type.Type` instance.
|
||||||
|
:param keys: The keys to delete
|
||||||
|
|
||||||
|
:returns: ``None``
|
||||||
|
"""
|
||||||
|
res = self._get_resource(_type.Type, type)
|
||||||
|
return res.delete_extra_specs(self, keys)
|
||||||
|
|
||||||
def get_type_encryption(self, volume_type_id):
|
def get_type_encryption(self, volume_type_id):
|
||||||
"""Get the encryption details of a volume type
|
"""Get the encryption details of a volume type
|
||||||
|
|
||||||
|
@@ -10,7 +10,9 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from openstack import exceptions
|
||||||
from openstack import resource
|
from openstack import resource
|
||||||
|
from openstack import utils
|
||||||
|
|
||||||
|
|
||||||
class Type(resource.Resource):
|
class Type(resource.Resource):
|
||||||
@@ -39,6 +41,59 @@ class Type(resource.Resource):
|
|||||||
#: a private volume-type. *Type: bool*
|
#: a private volume-type. *Type: bool*
|
||||||
is_public = resource.Body('os-volume-type-access:is_public', type=bool)
|
is_public = resource.Body('os-volume-type-access:is_public', type=bool)
|
||||||
|
|
||||||
|
def _extra_specs(self, method, key=None, delete=False,
|
||||||
|
extra_specs=None):
|
||||||
|
extra_specs = extra_specs or {}
|
||||||
|
for k, v in extra_specs.items():
|
||||||
|
if not isinstance(v, str):
|
||||||
|
raise ValueError("The value for %s (%s) must be "
|
||||||
|
"a text string" % (k, v))
|
||||||
|
|
||||||
|
if key is not None:
|
||||||
|
url = utils.urljoin(self.base_path, self.id, "extra_specs", key)
|
||||||
|
else:
|
||||||
|
url = utils.urljoin(self.base_path, self.id, "extra_specs")
|
||||||
|
|
||||||
|
kwargs = {}
|
||||||
|
if extra_specs:
|
||||||
|
kwargs["json"] = {"extra_specs": extra_specs}
|
||||||
|
|
||||||
|
response = method(url, headers={}, **kwargs)
|
||||||
|
|
||||||
|
# ensure Cinder API has not returned us an error
|
||||||
|
exceptions.raise_from_response(response)
|
||||||
|
# DELETE doesn't return a JSON body while everything else does.
|
||||||
|
return response.json() if not delete else None
|
||||||
|
|
||||||
|
def set_extra_specs(self, session, **extra_specs):
|
||||||
|
"""Update extra_specs
|
||||||
|
|
||||||
|
This call will replace only the extra_specs with the same keys
|
||||||
|
given here. Other keys will not be modified.
|
||||||
|
|
||||||
|
:param session: The session to use for this request.
|
||||||
|
:param kwargs extra_specs: key/value extra_specs pairs to be update on
|
||||||
|
this volume type. All keys and values
|
||||||
|
"""
|
||||||
|
if not extra_specs:
|
||||||
|
return dict()
|
||||||
|
|
||||||
|
result = self._extra_specs(session.post, extra_specs=extra_specs)
|
||||||
|
return result["extra_specs"]
|
||||||
|
|
||||||
|
def delete_extra_specs(self, session, keys):
|
||||||
|
"""Delete extra_specs
|
||||||
|
|
||||||
|
Note: This method will do a HTTP DELETE request for every key in keys.
|
||||||
|
|
||||||
|
:param session: The session to use for this request.
|
||||||
|
:param list keys: The keys to delete.
|
||||||
|
|
||||||
|
:rtype: ``None``
|
||||||
|
"""
|
||||||
|
for key in keys:
|
||||||
|
self._extra_specs(session.delete, key=key, delete=True)
|
||||||
|
|
||||||
|
|
||||||
class TypeEncryption(resource.Resource):
|
class TypeEncryption(resource.Resource):
|
||||||
resource_key = "encryption"
|
resource_key = "encryption"
|
||||||
|
@@ -75,6 +75,28 @@ class TestVolumeProxy(test_proxy_base.TestProxyBase):
|
|||||||
def test_type_update(self):
|
def test_type_update(self):
|
||||||
self.verify_update(self.proxy.update_type, type.Type)
|
self.verify_update(self.proxy.update_type, type.Type)
|
||||||
|
|
||||||
|
def test_type_extra_specs_update(self):
|
||||||
|
kwargs = {"a": "1", "b": "2"}
|
||||||
|
id = "an_id"
|
||||||
|
self._verify2(
|
||||||
|
"openstack.block_storage.v3.type.Type.set_extra_specs",
|
||||||
|
self.proxy.update_type_extra_specs,
|
||||||
|
method_args=[id],
|
||||||
|
method_kwargs=kwargs,
|
||||||
|
method_result=type.Type.existing(id=id,
|
||||||
|
extra_specs=kwargs),
|
||||||
|
expected_args=[self.proxy],
|
||||||
|
expected_kwargs=kwargs,
|
||||||
|
expected_result=kwargs)
|
||||||
|
|
||||||
|
def test_type_extra_specs_delete(self):
|
||||||
|
self._verify2(
|
||||||
|
"openstack.block_storage.v3.type.Type.delete_extra_specs",
|
||||||
|
self.proxy.delete_type_extra_specs,
|
||||||
|
expected_result=None,
|
||||||
|
method_args=["value", "key"],
|
||||||
|
expected_args=[self.proxy, "key"])
|
||||||
|
|
||||||
def test_type_encryption_get(self):
|
def test_type_encryption_get(self):
|
||||||
self.verify_get(self.proxy.get_type_encryption,
|
self.verify_get(self.proxy.get_type_encryption,
|
||||||
type.TypeEncryption,
|
type.TypeEncryption,
|
||||||
|
@@ -10,8 +10,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
from openstack.tests.unit import base
|
from openstack.tests.unit import base
|
||||||
|
|
||||||
|
from openstack import exceptions
|
||||||
from openstack.block_storage.v3 import type
|
from openstack.block_storage.v3 import type
|
||||||
|
|
||||||
FAKE_ID = "6685584b-1eac-4da6-b5c3-555430cf68ff"
|
FAKE_ID = "6685584b-1eac-4da6-b5c3-555430cf68ff"
|
||||||
@@ -27,6 +30,10 @@ TYPE = {
|
|||||||
|
|
||||||
class TestType(base.TestCase):
|
class TestType(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestType, self).setUp()
|
||||||
|
self.extra_specs_result = {"extra_specs": {"go": "cubs", "boo": "sox"}}
|
||||||
|
|
||||||
def test_basic(self):
|
def test_basic(self):
|
||||||
sot = type.Type(**TYPE)
|
sot = type.Type(**TYPE)
|
||||||
self.assertEqual("volume_type", sot.resource_key)
|
self.assertEqual("volume_type", sot.resource_key)
|
||||||
@@ -48,3 +55,72 @@ class TestType(base.TestCase):
|
|||||||
self.assertEqual(TYPE["extra_specs"], sot.extra_specs)
|
self.assertEqual(TYPE["extra_specs"], sot.extra_specs)
|
||||||
self.assertEqual(TYPE["name"], sot.name)
|
self.assertEqual(TYPE["name"], sot.name)
|
||||||
self.assertEqual(TYPE["description"], sot.description)
|
self.assertEqual(TYPE["description"], sot.description)
|
||||||
|
|
||||||
|
def test_set_extra_specs(self):
|
||||||
|
response = mock.Mock()
|
||||||
|
response.status_code = 200
|
||||||
|
response.json.return_value = self.extra_specs_result
|
||||||
|
sess = mock.Mock()
|
||||||
|
sess.post.return_value = response
|
||||||
|
|
||||||
|
sot = type.Type(id=FAKE_ID)
|
||||||
|
|
||||||
|
set_specs = {"lol": "rofl"}
|
||||||
|
|
||||||
|
result = sot.set_extra_specs(sess, **set_specs)
|
||||||
|
|
||||||
|
self.assertEqual(result, self.extra_specs_result["extra_specs"])
|
||||||
|
sess.post.assert_called_once_with("types/" + FAKE_ID + "/extra_specs",
|
||||||
|
headers={},
|
||||||
|
json={"extra_specs": set_specs})
|
||||||
|
|
||||||
|
def test_set_extra_specs_error(self):
|
||||||
|
sess = mock.Mock()
|
||||||
|
response = mock.Mock()
|
||||||
|
response.status_code = 400
|
||||||
|
response.content = None
|
||||||
|
sess.post.return_value = response
|
||||||
|
|
||||||
|
sot = type.Type(id=FAKE_ID)
|
||||||
|
|
||||||
|
set_specs = {"lol": "rofl"}
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.BadRequestException,
|
||||||
|
sot.set_extra_specs,
|
||||||
|
sess,
|
||||||
|
**set_specs)
|
||||||
|
|
||||||
|
def test_delete_extra_specs(self):
|
||||||
|
sess = mock.Mock()
|
||||||
|
response = mock.Mock()
|
||||||
|
response.status_code = 200
|
||||||
|
sess.delete.return_value = response
|
||||||
|
|
||||||
|
sot = type.Type(id=FAKE_ID)
|
||||||
|
|
||||||
|
key = "hey"
|
||||||
|
|
||||||
|
sot.delete_extra_specs(sess, [key])
|
||||||
|
|
||||||
|
sess.delete.assert_called_once_with(
|
||||||
|
"types/" + FAKE_ID + "/extra_specs/" + key,
|
||||||
|
headers={},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_extra_specs_error(self):
|
||||||
|
sess = mock.Mock()
|
||||||
|
response = mock.Mock()
|
||||||
|
response.status_code = 400
|
||||||
|
response.content = None
|
||||||
|
sess.delete.return_value = response
|
||||||
|
|
||||||
|
sot = type.Type(id=FAKE_ID)
|
||||||
|
|
||||||
|
key = "hey"
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.BadRequestException,
|
||||||
|
sot.delete_extra_specs,
|
||||||
|
sess,
|
||||||
|
[key])
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Add support for updating block storage volume type objects.
|
Reference in New Issue
Block a user