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:
parent
71a8466f0f
commit
2b7469cc85
@ -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
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -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)
|
||||
|
72
openstack/block_storage/v3/group_snapshot.py
Normal file
72
openstack/block_storage/v3/group_snapshot.py
Normal 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)
|
@ -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)
|
||||
|
51
openstack/tests/unit/block_storage/v3/test_group_snapshot.py
Normal file
51
openstack/tests/unit/block_storage/v3/test_group_snapshot.py
Normal 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)
|
@ -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)
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add support for group snapshots to the block storage service.
|
Loading…
Reference in New Issue
Block a user