block storage: Add support for group type specs
Add the ability to manipulate these. Change-Id: I6b0dc26044e0b1e49eb2e16bb72f77bf48a9f603 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
parent
19c070a405
commit
9e9fc98795
@ -70,6 +70,10 @@ Group Type Operations
|
||||
:noindex:
|
||||
:members: create_group_type, delete_group_type, update_group_type,
|
||||
get_group_type, find_group_type, group_types,
|
||||
fetch_group_type_group_specs, create_group_type_group_specs,
|
||||
get_group_type_group_specs_property,
|
||||
update_group_type_group_specs_property,
|
||||
delete_group_type_group_specs_property
|
||||
|
||||
Type Operations
|
||||
^^^^^^^^^^^^^^^
|
||||
|
@ -1082,6 +1082,66 @@ class Proxy(_base_proxy.BaseBlockStorageProxy):
|
||||
return self._update(
|
||||
_group_type.GroupType, group_type, **attrs)
|
||||
|
||||
def fetch_group_type_group_specs(self, group_type):
|
||||
"""Lists group specs of a group type.
|
||||
|
||||
:param group_type: Either the ID of a group type or a
|
||||
:class:`~openstack.block_storage.v3.group_type.GroupType` instance.
|
||||
|
||||
:returns: One :class:`~openstack.block_storage.v3.group_type.GroupType`
|
||||
"""
|
||||
group_type = self._get_resource(_group_type.GroupType, group_type)
|
||||
return group_type.fetch_group_specs(self)
|
||||
|
||||
def create_group_type_group_specs(self, group_type, group_specs):
|
||||
"""Create group specs for a group type.
|
||||
|
||||
:param group_type: Either the ID of a group type or a
|
||||
:class:`~openstack.block_storage.v3.group_type.GroupType` instance.
|
||||
:param dict group_specs: dict of extra specs
|
||||
|
||||
:returns: One :class:`~openstack.block_storage.v3.group_type.GroupType`
|
||||
"""
|
||||
group_type = self._get_resource(_group_type.GroupType, group_type)
|
||||
return group_type.create_group_specs(self, specs=group_specs)
|
||||
|
||||
def get_group_type_group_specs_property(self, group_type, prop):
|
||||
"""Retrieve a group spec property for a group type.
|
||||
|
||||
:param group_type: Either the ID of a group type or a
|
||||
:class:`~openstack.block_storage.v3.group_type.GroupType` instance.
|
||||
:param str prop: Property name.
|
||||
|
||||
:returns: String value of the requested property.
|
||||
"""
|
||||
group_type = self._get_resource(_group_type.GroupType, group_type)
|
||||
return group_type.get_group_specs_property(self, prop)
|
||||
|
||||
def update_group_type_group_specs_property(self, group_type, prop, val):
|
||||
"""Update a group spec property for a group type.
|
||||
|
||||
:param group_type: Either the ID of a group type or a
|
||||
:class:`~openstack.block_storage.v3.group_type.GroupType` instance.
|
||||
:param str prop: Property name.
|
||||
:param str val: Property value.
|
||||
|
||||
:returns: String value of the requested property.
|
||||
"""
|
||||
group_type = self._get_resource(_group_type.GroupType, group_type)
|
||||
return group_type.update_group_specs_property(self, prop, val)
|
||||
|
||||
def delete_group_type_group_specs_property(self, group_type, prop):
|
||||
"""Delete a group spec property from a group type.
|
||||
|
||||
:param group_type: Either the ID of a group type or a
|
||||
:class:`~openstack.block_storage.v3.group_type.GroupType` instance.
|
||||
:param str prop: Property name.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
group_type = self._get_resource(_group_type.GroupType, group_type)
|
||||
return group_type.delete_group_specs_property(self, prop)
|
||||
|
||||
# ====== QUOTA SETS ======
|
||||
def get_quota_set(self, project, usage=False, **query):
|
||||
"""Show quota set information for the project
|
||||
|
@ -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 GroupType(resource.Resource):
|
||||
@ -31,6 +33,90 @@ class GroupType(resource.Resource):
|
||||
#: The group type description.
|
||||
description = resource.Body("description")
|
||||
#: Contains the specifications for a group type.
|
||||
group_specs = resource.Body("group_specs", type=dict)
|
||||
group_specs = resource.Body("group_specs", type=dict, default={})
|
||||
#: Whether the group type is publicly visible.
|
||||
is_public = resource.Body("is_public", type=bool)
|
||||
|
||||
def fetch_group_specs(self, session):
|
||||
"""Fetch group_specs of the group type.
|
||||
|
||||
These are returned by default if the user has suitable permissions
|
||||
(i.e. you're an admin) but by default you also need the same
|
||||
permissions to access this API. That means this function is kind of
|
||||
useless. However, that is how the API was designed and it is
|
||||
theoretically possible that people will have modified their policy to
|
||||
allow this but not the other so we provide this anyway.
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
:returns: An updated version of this object.
|
||||
"""
|
||||
url = utils.urljoin(GroupType.base_path, self.id, 'group_specs')
|
||||
microversion = self._get_microversion_for(session, 'fetch')
|
||||
response = session.get(url, microversion=microversion)
|
||||
exceptions.raise_from_response(response)
|
||||
specs = response.json().get('group_specs', {})
|
||||
self._update(group_specs=specs)
|
||||
return self
|
||||
|
||||
def create_group_specs(self, session, specs):
|
||||
"""Creates group specs for the group type.
|
||||
|
||||
This will override whatever specs are already present on the group
|
||||
type.
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
:param specs: A dict of group specs to set on the group type.
|
||||
:returns: An updated version of this object.
|
||||
"""
|
||||
url = utils.urljoin(GroupType.base_path, self.id, 'group_specs')
|
||||
microversion = self._get_microversion_for(session, 'create')
|
||||
response = session.post(
|
||||
url, json={'group_specs': specs}, microversion=microversion,
|
||||
)
|
||||
exceptions.raise_from_response(response)
|
||||
specs = response.json().get('group_specs', {})
|
||||
self._update(group_specs=specs)
|
||||
return self
|
||||
|
||||
def get_group_specs_property(self, session, prop):
|
||||
"""Retrieve a group spec property of the group type.
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
:param prop: The name of the group spec property to update.
|
||||
:returns: The value of the group spec property.
|
||||
"""
|
||||
url = utils.urljoin(GroupType.base_path, self.id, 'group_specs', prop)
|
||||
microversion = self._get_microversion_for(session, 'fetch')
|
||||
response = session.get(url, microversion=microversion)
|
||||
exceptions.raise_from_response(response)
|
||||
val = response.json().get(prop)
|
||||
return val
|
||||
|
||||
def update_group_specs_property(self, session, prop, val):
|
||||
"""Update a group spec property of the group type.
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
:param prop: The name of the group spec property to update.
|
||||
:param val: The value to set for the group spec property.
|
||||
:returns: The updated value of the group spec property.
|
||||
"""
|
||||
url = utils.urljoin(GroupType.base_path, self.id, 'group_specs', prop)
|
||||
microversion = self._get_microversion_for(session, 'commit')
|
||||
response = session.put(
|
||||
url, json={prop: val}, microversion=microversion
|
||||
)
|
||||
exceptions.raise_from_response(response)
|
||||
val = response.json().get(prop)
|
||||
return val
|
||||
|
||||
def delete_group_specs_property(self, session, prop):
|
||||
"""Delete a group spec property from the group type.
|
||||
|
||||
:param session: The session to use for making this request.
|
||||
:param prop: The name of the group spec property to delete.
|
||||
:returns: None
|
||||
"""
|
||||
url = utils.urljoin(GroupType.base_path, self.id, 'group_specs', prop)
|
||||
microversion = self._get_microversion_for(session, 'delete')
|
||||
response = session.delete(url, microversion=microversion)
|
||||
exceptions.raise_from_response(response)
|
||||
|
103
openstack/tests/functional/block_storage/v3/test_group.py
Normal file
103
openstack/tests/functional/block_storage/v3/test_group.py
Normal file
@ -0,0 +1,103 @@
|
||||
# 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.
|
||||
|
||||
from openstack.block_storage.v3 import group_type as _group_type
|
||||
from openstack.tests.functional.block_storage.v3 import base
|
||||
|
||||
|
||||
class TestGroup(base.BaseBlockStorageTest):
|
||||
# TODO(stephenfin): We should use setUpClass here for MOAR SPEED!!!
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
if not self.user_cloud.has_service('block-storage'):
|
||||
self.skipTest('block-storage service not supported by cloud')
|
||||
|
||||
group_type_name = self.getUniqueString()
|
||||
self.group_type = self.conn.block_storage.create_group_type(
|
||||
name=group_type_name,
|
||||
)
|
||||
self.addCleanup(
|
||||
self.conn.block_storage.delete_group_type,
|
||||
self.group_type,
|
||||
)
|
||||
self.assertIsInstance(self.group_type, _group_type.GroupType)
|
||||
self.assertEqual(group_type_name, self.group_type.name)
|
||||
|
||||
def test_group_type(self):
|
||||
# get
|
||||
group_type = self.conn.block_storage.get_group_type(self.group_type)
|
||||
self.assertEqual(self.group_type.name, group_type.name)
|
||||
|
||||
# find
|
||||
group_type = self.conn.block_storage.find_group_type(
|
||||
self.group_type.name,
|
||||
)
|
||||
self.assertEqual(self.group_type.id, group_type.id)
|
||||
|
||||
# list
|
||||
group_types = list(self.conn.block_storage.group_types())
|
||||
# other tests may have created group types and there can be defaults so
|
||||
# we don't assert that this is the *only* group type present
|
||||
self.assertIn(self.group_type.id, {g.id for g in group_types})
|
||||
|
||||
# update
|
||||
group_type_name = self.getUniqueString()
|
||||
group_type_description = self.getUniqueString()
|
||||
group_type = self.conn.block_storage.update_group_type(
|
||||
self.group_type,
|
||||
name=group_type_name,
|
||||
description=group_type_description,
|
||||
)
|
||||
self.assertIsInstance(group_type, _group_type.GroupType)
|
||||
group_type = self.conn.block_storage.get_group_type(self.group_type.id)
|
||||
self.assertEqual(group_type_name, group_type.name)
|
||||
self.assertEqual(group_type_description, group_type.description)
|
||||
|
||||
def test_group_type_group_specs(self):
|
||||
# create
|
||||
group_type = self.conn.block_storage.create_group_type_group_specs(
|
||||
self.group_type,
|
||||
{'foo': 'bar', 'acme': 'buzz'},
|
||||
)
|
||||
self.assertIsInstance(group_type, _group_type.GroupType)
|
||||
group_type = self.conn.block_storage.get_group_type(self.group_type.id)
|
||||
self.assertEqual(
|
||||
{'foo': 'bar', 'acme': 'buzz'}, group_type.group_specs
|
||||
)
|
||||
|
||||
# get
|
||||
spec = self.conn.block_storage.get_group_type_group_specs_property(
|
||||
self.group_type,
|
||||
'foo',
|
||||
)
|
||||
self.assertEqual('bar', spec)
|
||||
|
||||
# update
|
||||
spec = self.conn.block_storage.update_group_type_group_specs_property(
|
||||
self.group_type,
|
||||
'foo',
|
||||
'baz',
|
||||
)
|
||||
self.assertEqual('baz', spec)
|
||||
group_type = self.conn.block_storage.get_group_type(self.group_type.id)
|
||||
self.assertEqual(
|
||||
{'foo': 'baz', 'acme': 'buzz'}, group_type.group_specs
|
||||
)
|
||||
|
||||
# delete
|
||||
self.conn.block_storage.delete_group_type_group_specs_property(
|
||||
self.group_type,
|
||||
'foo',
|
||||
)
|
||||
group_type = self.conn.block_storage.get_group_type(self.group_type.id)
|
||||
self.assertEqual({'acme': 'buzz'}, group_type.group_specs)
|
@ -1,39 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from openstack.block_storage.v3 import group_type as _group_type
|
||||
from openstack.tests.functional.block_storage.v3 import base
|
||||
|
||||
|
||||
class TestGroupType(base.BaseBlockStorageTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestGroupType, self).setUp()
|
||||
|
||||
self.GROUP_TYPE_NAME = self.getUniqueString()
|
||||
self.GROUP_TYPE_ID = None
|
||||
|
||||
group_type = self.conn.block_storage.create_group_type(
|
||||
name=self.GROUP_TYPE_NAME)
|
||||
self.assertIsInstance(group_type, _group_type.GroupType)
|
||||
self.assertEqual(self.GROUP_TYPE_NAME, group_type.name)
|
||||
self.GROUP_TYPE_ID = group_type.id
|
||||
|
||||
def tearDown(self):
|
||||
group_type = self.conn.block_storage.delete_group_type(
|
||||
self.GROUP_TYPE_ID, ignore_missing=False)
|
||||
self.assertIsNone(group_type)
|
||||
super(TestGroupType, self).tearDown()
|
||||
|
||||
def test_get(self):
|
||||
group_type = self.conn.block_storage.get_group_type(self.GROUP_TYPE_ID)
|
||||
self.assertEqual(self.GROUP_TYPE_NAME, group_type.name)
|
@ -10,6 +10,10 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from keystoneauth1 import adapter
|
||||
|
||||
from openstack.block_storage.v3 import group_type
|
||||
from openstack.tests.unit import base
|
||||
|
||||
@ -18,13 +22,16 @@ GROUP_TYPE = {
|
||||
"name": "grp-type-001",
|
||||
"description": "group type 001",
|
||||
"is_public": True,
|
||||
"group_specs": {
|
||||
"consistent_group_snapshot_enabled": "<is> False"
|
||||
}
|
||||
"group_specs": {"consistent_group_snapshot_enabled": "<is> False"},
|
||||
}
|
||||
|
||||
|
||||
class TestGroupType(base.TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.sess = mock.Mock(spec=adapter.Adapter)
|
||||
self.sess.default_microversion = 1
|
||||
self.sess._get_connection = mock.Mock(return_value=self.cloud)
|
||||
|
||||
def test_basic(self):
|
||||
resource = group_type.GroupType()
|
||||
@ -44,3 +51,93 @@ class TestGroupType(base.TestCase):
|
||||
self.assertEqual(GROUP_TYPE["description"], resource.description)
|
||||
self.assertEqual(GROUP_TYPE["is_public"], resource.is_public)
|
||||
self.assertEqual(GROUP_TYPE["group_specs"], resource.group_specs)
|
||||
|
||||
def test_fetch_group_specs(self):
|
||||
sot = group_type.GroupType(**GROUP_TYPE)
|
||||
resp = mock.Mock()
|
||||
resp.body = {'group_specs': {'a': 'b', 'c': 'd'}}
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
resp.status_code = 200
|
||||
self.sess.get = mock.Mock(return_value=resp)
|
||||
|
||||
rsp = sot.fetch_group_specs(self.sess)
|
||||
|
||||
self.sess.get.assert_called_with(
|
||||
f"group_types/{GROUP_TYPE['id']}/group_specs",
|
||||
microversion=self.sess.default_microversion,
|
||||
)
|
||||
|
||||
self.assertEqual(resp.body['group_specs'], rsp.group_specs)
|
||||
self.assertIsInstance(rsp, group_type.GroupType)
|
||||
|
||||
def test_create_group_specs(self):
|
||||
sot = group_type.GroupType(**GROUP_TYPE)
|
||||
specs = {'a': 'b', 'c': 'd'}
|
||||
resp = mock.Mock()
|
||||
resp.body = {'group_specs': specs}
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
resp.status_code = 200
|
||||
self.sess.post = mock.Mock(return_value=resp)
|
||||
|
||||
rsp = sot.create_group_specs(self.sess, specs)
|
||||
|
||||
self.sess.post.assert_called_with(
|
||||
f"group_types/{GROUP_TYPE['id']}/group_specs",
|
||||
json={'group_specs': specs},
|
||||
microversion=self.sess.default_microversion,
|
||||
)
|
||||
|
||||
self.assertEqual(resp.body['group_specs'], rsp.group_specs)
|
||||
self.assertIsInstance(rsp, group_type.GroupType)
|
||||
|
||||
def test_get_group_specs_property(self):
|
||||
sot = group_type.GroupType(**GROUP_TYPE)
|
||||
resp = mock.Mock()
|
||||
resp.body = {'a': 'b'}
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
resp.status_code = 200
|
||||
self.sess.get = mock.Mock(return_value=resp)
|
||||
|
||||
rsp = sot.get_group_specs_property(self.sess, 'a')
|
||||
|
||||
self.sess.get.assert_called_with(
|
||||
f"group_types/{GROUP_TYPE['id']}/group_specs/a",
|
||||
microversion=self.sess.default_microversion,
|
||||
)
|
||||
|
||||
self.assertEqual('b', rsp)
|
||||
|
||||
def test_update_group_specs_property(self):
|
||||
sot = group_type.GroupType(**GROUP_TYPE)
|
||||
resp = mock.Mock()
|
||||
resp.body = {'a': 'b'}
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
resp.status_code = 200
|
||||
self.sess.put = mock.Mock(return_value=resp)
|
||||
|
||||
rsp = sot.update_group_specs_property(self.sess, 'a', 'b')
|
||||
|
||||
self.sess.put.assert_called_with(
|
||||
f"group_types/{GROUP_TYPE['id']}/group_specs/a",
|
||||
json={'a': 'b'},
|
||||
microversion=self.sess.default_microversion,
|
||||
)
|
||||
|
||||
self.assertEqual('b', rsp)
|
||||
|
||||
def test_delete_group_specs_property(self):
|
||||
sot = group_type.GroupType(**GROUP_TYPE)
|
||||
resp = mock.Mock()
|
||||
resp.body = None
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
resp.status_code = 200
|
||||
self.sess.delete = mock.Mock(return_value=resp)
|
||||
|
||||
rsp = sot.delete_group_specs_property(self.sess, 'a')
|
||||
|
||||
self.sess.delete.assert_called_with(
|
||||
f"group_types/{GROUP_TYPE['id']}/group_specs/a",
|
||||
microversion=self.sess.default_microversion,
|
||||
)
|
||||
|
||||
self.assertIsNone(rsp)
|
||||
|
@ -169,6 +169,47 @@ class TestGroupType(TestVolumeProxy):
|
||||
def test_group_type_update(self):
|
||||
self.verify_update(self.proxy.update_group_type, group_type.GroupType)
|
||||
|
||||
def test_group_type_fetch_group_specs(self):
|
||||
self._verify(
|
||||
"openstack.block_storage.v3.group_type.GroupType.fetch_group_specs", # noqa: E501
|
||||
self.proxy.fetch_group_type_group_specs,
|
||||
method_args=["value"],
|
||||
expected_args=[self.proxy],
|
||||
)
|
||||
|
||||
def test_group_type_create_group_specs(self):
|
||||
self._verify(
|
||||
"openstack.block_storage.v3.group_type.GroupType.create_group_specs", # noqa: E501
|
||||
self.proxy.create_group_type_group_specs,
|
||||
method_args=["value", {'a': 'b'}],
|
||||
expected_args=[self.proxy],
|
||||
expected_kwargs={"specs": {'a': 'b'}},
|
||||
)
|
||||
|
||||
def test_group_type_get_group_specs_prop(self):
|
||||
self._verify(
|
||||
"openstack.block_storage.v3.group_type.GroupType.get_group_specs_property", # noqa: E501
|
||||
self.proxy.get_group_type_group_specs_property,
|
||||
method_args=["value", "prop"],
|
||||
expected_args=[self.proxy, "prop"],
|
||||
)
|
||||
|
||||
def test_group_type_update_group_specs_prop(self):
|
||||
self._verify(
|
||||
"openstack.block_storage.v3.group_type.GroupType.update_group_specs_property", # noqa: E501
|
||||
self.proxy.update_group_type_group_specs_property,
|
||||
method_args=["value", "prop", "val"],
|
||||
expected_args=[self.proxy, "prop", "val"],
|
||||
)
|
||||
|
||||
def test_group_type_delete_group_specs_prop(self):
|
||||
self._verify(
|
||||
"openstack.block_storage.v3.group_type.GroupType.delete_group_specs_property", # noqa: E501
|
||||
self.proxy.delete_group_type_group_specs_property,
|
||||
method_args=["value", "prop"],
|
||||
expected_args=[self.proxy, "prop"],
|
||||
)
|
||||
|
||||
|
||||
class TestExtension(TestVolumeProxy):
|
||||
def test_extensions(self):
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add support for creating, updating and deleting group type group specs for
|
||||
the block storage service.
|
Loading…
Reference in New Issue
Block a user