block storage: Add support for the GroupSnapshot resource

Introduce the GroupSnapshot resource, fill in its resources, and
implement API calls to support the Cinder v3 API

Change-Id: I1189285892d64935912830d22fd2f7e59a797e87
Task: 41855
Story: 2008619
Task: 42074
Story: 2008621
This commit is contained in:
Dylan Zapzalka 2021-03-17 15:58:39 -05:00 committed by Artem Goncharov
parent 71a8466f0f
commit 2b7469cc85
7 changed files with 336 additions and 1 deletions

View File

@ -71,6 +71,14 @@ Group Operations
:members: create_group, create_group_from_source, delete_group, update_group,
get_group, find_group, groups, reset_group_state
Group Snapshot Operations
^^^^^^^^^^^^^^^^^^^^^^^^^
.. autoclass:: openstack.block_storage.v3._proxy.Proxy
:noindex:
:members: create_group_snapshot, delete_group_snapshot, get_group_snapshot,
find_group_snapshot, group_snapshots, reset_group_snapshot_state
Group Type Operations
^^^^^^^^^^^^^^^^^^^^^

View File

@ -16,6 +16,7 @@ from openstack.block_storage.v3 import backup as _backup
from openstack.block_storage.v3 import capabilities as _capabilities
from openstack.block_storage.v3 import extension as _extension
from openstack.block_storage.v3 import group as _group
from openstack.block_storage.v3 import group_snapshot as _group_snapshot
from openstack.block_storage.v3 import group_type as _group_type
from openstack.block_storage.v3 import limits as _limits
from openstack.block_storage.v3 import quota_set as _quota_set
@ -1103,6 +1104,91 @@ class Proxy(_base_proxy.BaseBlockStorageProxy):
return self._list(availability_zone.AvailabilityZone)
# ====== GROUP SNAPSHOT ======
def get_group_snapshot(self, group_snapshot_id):
"""Get a group snapshot
:param group_snapshot_id: The ID of the group snapshot to get.
:returns: A GroupSnapshot instance.
:rtype: :class:`~openstack.block_storage.v3.group_snapshot`
"""
return self._get(_group_snapshot.GroupSnapshot, group_snapshot_id)
def find_group_snapshot(self, name_or_id, ignore_missing=True):
"""Find a single group snapshot
:param name_or_id: The name or ID of a group snapshot.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be raised
when the group snapshot does not exist.
:returns: One :class:`~openstack.block_storage.v3.group_snapshot`
:raises: :class:`~openstack.exceptions.ResourceNotFound`
when no resource can be found.
"""
return self._find(
_group_snapshot.GroupSnapshot, name_or_id,
ignore_missing=ignore_missing)
def group_snapshots(self, details=True, **query):
"""Retrieve a generator of group snapshots
:param bool details: When ``True``, returns
:class:`~openstack.block_storage.v3.group_snapshot.GroupSnapshot`
objects with additional attributes filled.
:param kwargs query: Optional query parameters to be sent to limit
the group snapshots being returned.
:returns: A generator of group snapshtos.
"""
base_path = '/group_snapshots'
if details:
base_path = '/group_snapshots/detail'
return self._list(
_group_snapshot.GroupSnapshot,
base_path=base_path,
**query,
)
def create_group_snapshot(self, **attrs):
"""Create a group snapshot
:param dict attrs: Keyword arguments which will be used to create a
:class:`~openstack.block_storage.v3.group_snapshot.GroupSnapshot`
comprised of the properties on the GroupSnapshot class.
:returns: The results of group snapshot creation.
:rtype: :class:`~openstack.block_storage.v3.group_snapshot`.
"""
return self._create(_group_snapshot.GroupSnapshot, **attrs)
def reset_group_snapshot_state(self, group_snapshot, state):
"""Reset group snapshot status
:param group_snapshot: The
:class:`~openstack.block_storage.v3.group_snapshot.GroupSnapshot`
to set the state.
:param state: The state of the group snapshot to be set.
:returns: None
"""
resource = self._get_resource(
_group_snapshot.GroupSnapshot, group_snapshot)
resource.reset_state(self, state)
def delete_group_snapshot(self, group_snapshot, ignore_missing=True):
"""Delete a group snapshot
:param group_snapshot: The :class:`~openstack.block_storage.v3.
group_snapshot.GroupSnapshot` to delete.
:returns: None
"""
self._delete(
_group_snapshot.GroupSnapshot, group_snapshot,
ignore_missing=ignore_missing)
# ====== GROUP TYPE ======
def get_group_type(self, group_type):
"""Get a specific group type
@ -1180,7 +1266,7 @@ class Proxy(_base_proxy.BaseBlockStorageProxy):
When set to ``True``, no exception will be set when attempting to
delete a nonexistent zone.
:returns: ''None''
:returns: None
"""
self._delete(
_group_type.GroupType, group_type, ignore_missing=ignore_missing)

View File

@ -0,0 +1,72 @@
# 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 import exceptions
from openstack import resource
from openstack import utils
class GroupSnapshot(resource.Resource):
resource_key = "group_snapshot"
resources_key = "group_snapshots"
base_path = "/group_snapshots"
# capabilities
allow_fetch = True
allow_create = True
allow_delete = True
allow_commit = False
allow_list = True
#: Properties
#: The date and time when the resource was created.
created_at = resource.Body("created_at")
#: The group snapshot description.
description = resource.Body("description")
#: The UUID of the source group.
group_id = resource.Body("group_id")
#: The group type ID.
group_type_id = resource.Body("group_type_id")
#: The ID of the group snapshot.
id = resource.Body("id")
#: The group snapshot name.
name = resource.Body("name")
#: The UUID of the volume group snapshot project.
project_id = resource.Body("project_id")
#: The status of the generic group snapshot.
status = resource.Body("status")
# Pagination support was added in microversion 3.29
_max_microversion = '3.29'
def _action(self, session, body, microversion=None):
"""Preform aggregate actions given the message body."""
url = utils.urljoin(self.base_path, self.id, 'action')
headers = {'Accept': ''}
# TODO(stephenfin): This logic belongs in openstack.resource I suspect
if microversion is None:
if session.default_microversion:
microversion = session.default_microversion
else:
microversion = utils.maximum_supported_microversion(
session, self._max_microversion,
)
response = session.post(
url, json=body, headers=headers, microversion=microversion,
)
exceptions.raise_from_response(response)
return response
def reset_state(self, session, state):
"""Resets the status for a group snapshot."""
body = {'reset_status': {'status': state}}
return self._action(session, body)

View File

@ -11,7 +11,9 @@
# under the License.
from openstack.block_storage.v3 import group as _group
from openstack.block_storage.v3 import group_snapshot as _group_snapshot
from openstack.block_storage.v3 import group_type as _group_type
from openstack.block_storage.v3 import volume as _volume
from openstack.tests.functional.block_storage.v3 import base
@ -151,3 +153,65 @@ class TestGroup(base.BaseBlockStorageTest):
group = self.conn.block_storage.get_group(self.group.id)
self.assertEqual(group_name, group.name)
self.assertEqual(group_description, group.description)
def test_group_snapshot(self):
# group snapshots require a volume
# no need for a teardown as the deletion of the group (with the
# 'delete_volumes' flag) will handle this but we do need to wait for
# the thing to be created
volume_name = self.getUniqueString()
self.volume = self.conn.block_storage.create_volume(
name=volume_name,
volume_type=self.volume_type.id,
group_id=self.group.id,
size=1,
)
self.conn.block_storage.wait_for_status(
self.volume,
status='available',
failures=['error'],
interval=2,
wait=self._wait_for_timeout,
)
self.assertIsInstance(self.volume, _volume.Volume)
group_snapshot_name = self.getUniqueString()
self.group_snapshot = self.conn.block_storage.create_group_snapshot(
name=group_snapshot_name,
group_id=self.group.id,
)
self.conn.block_storage.wait_for_status(
self.group_snapshot,
status='available',
failures=['error'],
interval=2,
wait=self._wait_for_timeout,
)
self.assertIsInstance(
self.group_snapshot,
_group_snapshot.GroupSnapshot,
)
# get
group_snapshot = self.conn.block_storage.get_group_snapshot(
self.group_snapshot.id,
)
self.assertEqual(self.group_snapshot.name, group_snapshot.name)
# find
group_snapshot = self.conn.block_storage.find_group_snapshot(
self.group_snapshot.name,
)
self.assertEqual(self.group_snapshot.id, group_snapshot.id)
# list
group_snapshots = self.conn.block_storage.group_snapshots()
# other tests may have created group snapshot and there can be defaults
# so we don't assert that this is the *only* group snapshot present
self.assertIn(self.group_snapshot.id, {g.id for g in group_snapshots})
# update (not supported)
# delete
self.conn.block_storage.delete_group_snapshot(self.group_snapshot)
self.conn.block_storage.wait_for_delete(self.group_snapshot)

View File

@ -0,0 +1,51 @@
# 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_snapshot
from openstack.tests.unit import base
GROUP_SNAPSHOT = {
"id": "6f519a48-3183-46cf-a32f-41815f813986",
"group_id": "6f519a48-3183-46cf-a32f-41815f814444",
"status": "available",
"created_at": "2015-09-16T09:28:52.000000",
"name": "my_group_snapshot1",
"description": "my first group snapshot",
"group_type_id": "7270c56e-6354-4528-8e8b-f54dee2232c8",
"project_id": "7ccf4863071f44aeb8f141f65780c51b",
}
class TestGroupSnapshot(base.TestCase):
def test_basic(self):
resource = group_snapshot.GroupSnapshot()
self.assertEqual("group_snapshot", resource.resource_key)
self.assertEqual("group_snapshots", resource.resources_key)
self.assertEqual("/group_snapshots", resource.base_path)
self.assertTrue(resource.allow_create)
self.assertTrue(resource.allow_fetch)
self.assertTrue(resource.allow_delete)
self.assertTrue(resource.allow_list)
self.assertFalse(resource.allow_commit)
def test_make_resource(self):
resource = group_snapshot.GroupSnapshot(**GROUP_SNAPSHOT)
self.assertEqual(GROUP_SNAPSHOT["created_at"], resource.created_at)
self.assertEqual(GROUP_SNAPSHOT["description"], resource.description)
self.assertEqual(GROUP_SNAPSHOT["group_id"], resource.group_id)
self.assertEqual(
GROUP_SNAPSHOT["group_type_id"], resource.group_type_id
)
self.assertEqual(GROUP_SNAPSHOT["id"], resource.id)
self.assertEqual(GROUP_SNAPSHOT["name"], resource.name)
self.assertEqual(GROUP_SNAPSHOT["project_id"], resource.project_id)
self.assertEqual(GROUP_SNAPSHOT["status"], resource.status)

View File

@ -17,6 +17,7 @@ from openstack.block_storage.v3 import backup
from openstack.block_storage.v3 import capabilities
from openstack.block_storage.v3 import extension
from openstack.block_storage.v3 import group
from openstack.block_storage.v3 import group_snapshot
from openstack.block_storage.v3 import group_type
from openstack.block_storage.v3 import limits
from openstack.block_storage.v3 import quota_set
@ -181,6 +182,55 @@ class TestGroup(TestVolumeProxy):
self._verify(self.proxy.reset_group_state, group.Group)
class TestGroupSnapshot(TestVolumeProxy):
def test_group_snapshot_get(self):
self.verify_get(
self.proxy.get_group_snapshot, group_snapshot.GroupSnapshot
)
def test_group_snapshot_find(self):
self.verify_find(
self.proxy.find_group_snapshot, group_snapshot.GroupSnapshot
)
def test_group_snapshots(self):
self.verify_list(
self.proxy.group_snapshots,
group_snapshot.GroupSnapshot,
expected_kwargs={},
)
def test_group_snapshots__detailed(self):
self.verify_list(
self.proxy.group_snapshots,
group_snapshot.GroupSnapshot,
method_kwargs={'details': True, 'query': 1},
expected_kwargs={
'query': 1,
'base_path': '/group_snapshots/detail',
},
)
def test_group_snapshot_create(self):
self.verify_create(
self.proxy.create_group_snapshot, group_snapshot.GroupSnapshot
)
def test_group_snapshot_delete(self):
self.verify_delete(
self.proxy.delete_group_snapshot,
group_snapshot.GroupSnapshot,
False,
)
def test_group_snapshot_delete_ignore(self):
self.verify_delete(
self.proxy.delete_group_snapshot,
group_snapshot.GroupSnapshot,
True,
)
class TestGroupType(TestVolumeProxy):
def test_group_type_get(self):
self.verify_get(self.proxy.get_group_type, group_type.GroupType)

View File

@ -0,0 +1,4 @@
---
features:
- |
Add support for group snapshots to the block storage service.