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:
parent
cc7369c743
commit
b4813fb83c
@ -175,6 +175,36 @@ class Proxy(_base_proxy.BaseBlockStorageProxy):
|
||||
"""
|
||||
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):
|
||||
"""Get the encryption details of a volume type
|
||||
|
||||
|
@ -10,7 +10,9 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from openstack import exceptions
|
||||
from openstack import resource
|
||||
from openstack import utils
|
||||
|
||||
|
||||
class Type(resource.Resource):
|
||||
@ -39,6 +41,59 @@ class Type(resource.Resource):
|
||||
#: a private volume-type. *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):
|
||||
resource_key = "encryption"
|
||||
|
@ -75,6 +75,28 @@ class TestVolumeProxy(test_proxy_base.TestProxyBase):
|
||||
def test_type_update(self):
|
||||
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):
|
||||
self.verify_get(self.proxy.get_type_encryption,
|
||||
type.TypeEncryption,
|
||||
|
@ -10,8 +10,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from openstack.tests.unit import base
|
||||
|
||||
from openstack import exceptions
|
||||
from openstack.block_storage.v3 import type
|
||||
|
||||
FAKE_ID = "6685584b-1eac-4da6-b5c3-555430cf68ff"
|
||||
@ -27,6 +30,10 @@ TYPE = {
|
||||
|
||||
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):
|
||||
sot = type.Type(**TYPE)
|
||||
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["name"], sot.name)
|
||||
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.
|
Loading…
Reference in New Issue
Block a user