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:
Stephen Finucane 2022-06-24 12:23:21 +01:00
parent 19c070a405
commit 9e9fc98795
8 changed files with 400 additions and 43 deletions

View File

@ -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
^^^^^^^^^^^^^^^

View File

@ -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

View File

@ -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)

View 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)

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -0,0 +1,5 @@
---
features:
- |
Add support for creating, updating and deleting group type group specs for
the block storage service.