diff --git a/doc/source/user/proxies/shared_file_system.rst b/doc/source/user/proxies/shared_file_system.rst index df02c4b74..0ea5925d5 100644 --- a/doc/source/user/proxies/shared_file_system.rst +++ b/doc/source/user/proxies/shared_file_system.rst @@ -66,3 +66,15 @@ service. .. autoclass:: openstack.shared_file_system.v2._proxy.Proxy :noindex: :members: limits + + +Shared File System Snapshots +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Interact with Share Snapshots supported by the Shared File Systems +service. + +.. autoclass:: openstack.shared_file_system.v2._proxy.Proxy + :noindex: + :members: share_snapshots, get_share_snapshot, delete_share_snapshot, + update_share_snapshot, create_share_snapshot diff --git a/doc/source/user/resources/shared_file_system/index.rst b/doc/source/user/resources/shared_file_system/index.rst index 459eb403a..0a4cc5b4e 100644 --- a/doc/source/user/resources/shared_file_system/index.rst +++ b/doc/source/user/resources/shared_file_system/index.rst @@ -9,3 +9,4 @@ Shared File System service resources v2/limit v2/share v2/user_message + v2/share_snapshot diff --git a/doc/source/user/resources/shared_file_system/v2/share_snapshot.rst b/doc/source/user/resources/shared_file_system/v2/share_snapshot.rst new file mode 100644 index 000000000..4063c4873 --- /dev/null +++ b/doc/source/user/resources/shared_file_system/v2/share_snapshot.rst @@ -0,0 +1,13 @@ +openstack.shared_file_system.v2.share_snapshot +============================================== + +.. automodule:: openstack.shared_file_system.v2.share_snapshot + +The ShareSnapshot Class +----------------------- + +The ``ShareSnapshot`` class inherits from +:class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.shared_file_system.v2.share_snapshot.ShareSnapshot + :members: diff --git a/openstack/shared_file_system/v2/_proxy.py b/openstack/shared_file_system/v2/_proxy.py index 2d4c32f60..4d34fd293 100644 --- a/openstack/shared_file_system/v2/_proxy.py +++ b/openstack/shared_file_system/v2/_proxy.py @@ -14,6 +14,9 @@ from openstack import proxy from openstack import resource from openstack.shared_file_system.v2 import ( availability_zone as _availability_zone) +from openstack.shared_file_system.v2 import ( + share_snapshot as _share_snapshot +) from openstack.shared_file_system.v2 import ( storage_pool as _storage_pool ) @@ -238,3 +241,74 @@ class Proxy(proxy.Proxy): """ return self._list( _limit.Limit, **query) + + def share_snapshots(self, details=True, **query): + """Lists all share snapshots with details. + + :param kwargs query: Optional query parameters to be sent to limit + the snapshots being returned. Available parameters include: + + * project_id: The ID of the user or service making the API request. + + :returns: A generator of manila share snapshot resources + :rtype: :class:`~openstack.shared_file_system.v2. + share_snapshot.ShareSnapshot` + """ + base_path = '/snapshots/detail' if details else None + return self._list( + _share_snapshot.ShareSnapshot, base_path=base_path, **query) + + def get_share_snapshot(self, snapshot_id): + """Lists details of a single share snapshot + + :param snapshot_id: The ID of the snapshot to get + :returns: Details of the identified share snapshot + :rtype: :class:`~openstack.shared_file_system.v2. + share_snapshot.ShareSnapshot` + """ + return self._get(_share_snapshot.ShareSnapshot, snapshot_id) + + def create_share_snapshot(self, **attrs): + """Creates a share snapshot from attributes + + :returns: Details of the new share snapshot + :rtype: :class:`~openstack.shared_file_system.v2. + share_snapshot.ShareSnapshot` + """ + return self._create(_share_snapshot.ShareSnapshot, **attrs) + + def update_share_snapshot(self, snapshot_id, **attrs): + """Updates details of a single share. + + :param snapshot_id: The ID of the snapshot to update + :pram dict attrs: The attributes to update on the snapshot + :returns: the updated share snapshot + :rtype: :class:`~openstack.shared_file_system.v2. + share_snapshot.ShareSnapshot` + """ + return self._update(_share_snapshot.ShareSnapshot, snapshot_id, + **attrs) + + def delete_share_snapshot(self, snapshot_id, ignore_missing=True): + """Deletes a single share snapshot + + :param snapshot_id: The ID of the snapshot to delete + :returns: Result of the ``delete`` + :rtype: ``None`` + """ + self._delete(_share_snapshot.ShareSnapshot, snapshot_id, + ignore_missing=ignore_missing) + + def wait_for_delete(self, res, interval=2, wait=120): + """Wait for a resource to be deleted. + :param res: The resource to wait on to be deleted. + :type resource: A :class:`~openstack.resource.Resource` object. + :param interval: Number of seconds to wait before to consecutive + checks. Default to 2. + :param wait: Maximum number of seconds to wait before the change. + Default to 120. + :returns: The resource is returned on success. + :raises: :class:`~openstack.exceptions.ResourceTimeout` if transition + to delete failed to occur in the specified seconds. + """ + return resource.wait_for_delete(self, res, interval, wait) diff --git a/openstack/shared_file_system/v2/share_snapshot.py b/openstack/shared_file_system/v2/share_snapshot.py new file mode 100644 index 000000000..c270c23aa --- /dev/null +++ b/openstack/shared_file_system/v2/share_snapshot.py @@ -0,0 +1,58 @@ +# 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 resource + + +class ShareSnapshot(resource.Resource): + resource_key = "snapshot" + resources_key = "snapshots" + base_path = "/snapshots" + + # capabilities + allow_create = True + allow_fetch = True + allow_commit = True + allow_delete = True + allow_list = True + allow_head = False + + _query_mapping = resource.QueryParameters( + "snapshot_id" + ) + + #: Properties + #: The date and time stamp when the resource was + #: created within the service’s database. + created_at = resource.Body("created_at") + #: The user defined description of the resource. + description = resource.Body("description", type=str) + #: The user defined name of the resource. + display_name = resource.Body("display_name", type=str) + #: The user defined description of the resource + display_description = resource.Body( + "display_description", type=str) + #: ID of the project that the snapshot belongs to. + project_id = resource.Body("project_id", type=str) + #: The UUID of the source share that was used to + #: create the snapshot. + share_id = resource.Body("share_id", type=str) + #: The file system protocol of a share snapshot + share_proto = resource.Body("share_proto", type=str) + #: The snapshot's source share's size, in GiBs. + share_size = resource.Body("share_size", type=int) + #: The snapshot size, in GiBs. + size = resource.Body("size", type=int) + #: The snapshot status + status = resource.Body("status", type=str) + #: ID of the user that the snapshot was created by. + user_id = resource.Body("user_id", type=str) diff --git a/openstack/tests/functional/shared_file_system/test_share_snapshot.py b/openstack/tests/functional/shared_file_system/test_share_snapshot.py new file mode 100644 index 000000000..43bf4229a --- /dev/null +++ b/openstack/tests/functional/shared_file_system/test_share_snapshot.py @@ -0,0 +1,84 @@ +# 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.tests.functional.shared_file_system import base + + +class ShareSnapshotTest(base.BaseSharedFileSystemTest): + + def setUp(self): + super(ShareSnapshotTest, self).setUp() + + self.SHARE_NAME = self.getUniqueString() + self.SNAPSHOT_NAME = self.getUniqueString() + my_share = self.operator_cloud.shared_file_system.create_share( + name=self.SHARE_NAME, size=2, share_type="dhss_false", + share_protocol='NFS', description=None) + self.operator_cloud.shared_file_system.wait_for_status( + my_share, + status='available', + failures=['error'], + interval=5, + wait=self._wait_for_timeout) + self.assertIsNotNone(my_share) + self.assertIsNotNone(my_share.id) + self.SHARE_ID = my_share.id + + msp = self.operator_cloud.shared_file_system.create_share_snapshot( + share_id=self.SHARE_ID, name=self.SNAPSHOT_NAME, force=True) + self.operator_cloud.shared_file_system.wait_for_status( + msp, + status='available', + failures=['error'], + interval=5, + wait=self._wait_for_timeout) + self.assertIsNotNone(msp.id) + self.SNAPSHOT_ID = msp.id + + def tearDown(self): + snpt = self.operator_cloud.shared_file_system.get_share_snapshot( + self.SNAPSHOT_ID) + sot = self.operator_cloud.shared_file_system.delete_share_snapshot( + snpt, ignore_missing=False + ) + self.operator_cloud.shared_file_system.wait_for_delete( + snpt, interval=2, wait=self._wait_for_timeout) + self.assertIsNone(sot) + sot = self.operator_cloud.shared_file_system.delete_share( + self.SHARE_ID, + ignore_missing=False) + self.assertIsNone(sot) + super(ShareSnapshotTest, self).tearDown() + + def test_get(self): + sot = self.operator_cloud.shared_file_system.get_share_snapshot( + self.SNAPSHOT_ID + ) + self.assertEqual(self.SNAPSHOT_NAME, sot.name) + + def test_list(self): + snaps = self.operator_cloud.shared_file_system.share_snapshots( + details=True + ) + self.assertGreater(len(list(snaps)), 0) + for snap in snaps: + for attribute in ('id', 'name', 'created_at', 'updated_at', + 'description', 'share_id', 'share_proto', + 'share_size', 'size', 'status', 'user_id'): + self.assertTrue(hasattr(snap, attribute)) + + def test_update(self): + u_snap = self.operator_cloud.shared_file_system.update_share_snapshot( + self.SNAPSHOT_ID, display_description='updated share snapshot') + get_u_snap = self.operator_cloud.shared_file_system.get_share_snapshot( + u_snap.id) + self.assertEqual('updated share snapshot', get_u_snap.description) diff --git a/openstack/tests/unit/shared_file_system/v2/test_proxy.py b/openstack/tests/unit/shared_file_system/v2/test_proxy.py index 573a354d2..2f6121d4c 100644 --- a/openstack/tests/unit/shared_file_system/v2/test_proxy.py +++ b/openstack/tests/unit/shared_file_system/v2/test_proxy.py @@ -15,6 +15,7 @@ from unittest import mock from openstack.shared_file_system.v2 import _proxy from openstack.shared_file_system.v2 import limit from openstack.shared_file_system.v2 import share +from openstack.shared_file_system.v2 import share_snapshot from openstack.shared_file_system.v2 import storage_pool from openstack.shared_file_system.v2 import user_message from openstack.tests.unit import test_proxy_base @@ -111,3 +112,59 @@ class TestUserMessageProxy(test_proxy_base.TestProxyBase): def test_limit(self): self.verify_list(self.proxy.limits, limit.Limit) + + +class TestShareSnapshotResource(test_proxy_base.TestProxyBase): + + def setUp(self): + super(TestShareSnapshotResource, self).setUp() + self.proxy = _proxy.Proxy(self.session) + + def test_share_snapshots(self): + self.verify_list( + self.proxy.share_snapshots, share_snapshot.ShareSnapshot) + + def test_share_snapshots_detailed(self): + self.verify_list( + self.proxy.share_snapshots, + share_snapshot.ShareSnapshot, + method_kwargs={"details": True, "name": "my_snapshot"}, + expected_kwargs={"name": "my_snapshot"}) + + def test_share_snapshots_not_detailed(self): + self.verify_list( + self.proxy.share_snapshots, + share_snapshot.ShareSnapshot, + method_kwargs={"details": False, "name": "my_snapshot"}, + expected_kwargs={"name": "my_snapshot"}) + + def test_share_snapshot_get(self): + self.verify_get( + self.proxy.get_share_snapshot, share_snapshot.ShareSnapshot) + + def test_share_snapshot_delete(self): + self.verify_delete( + self.proxy.delete_share_snapshot, + share_snapshot.ShareSnapshot, False) + + def test_share_snapshot_delete_ignore(self): + self.verify_delete( + self.proxy.delete_share_snapshot, + share_snapshot.ShareSnapshot, True) + + def test_share_snapshot_create(self): + self.verify_create( + self.proxy.create_share_snapshot, share_snapshot.ShareSnapshot) + + def test_share_snapshot_update(self): + self.verify_update( + self.proxy.update_share_snapshot, share_snapshot.ShareSnapshot) + + @mock.patch("openstack.resource.wait_for_delete") + def test_wait_for_delete(self, mock_wait): + mock_resource = mock.Mock() + mock_wait.return_value = mock_resource + + self.proxy.wait_for_delete(mock_resource) + + mock_wait.assert_called_once_with(self.proxy, mock_resource, 2, 120) diff --git a/openstack/tests/unit/shared_file_system/v2/test_share_snapshot.py b/openstack/tests/unit/shared_file_system/v2/test_share_snapshot.py new file mode 100644 index 000000000..744e7269e --- /dev/null +++ b/openstack/tests/unit/shared_file_system/v2/test_share_snapshot.py @@ -0,0 +1,65 @@ +# 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.shared_file_system.v2 import share_snapshot +from openstack.tests.unit import base + + +EXAMPLE = { + "status": "creating", + "share_id": "406ea93b-32e9-4907-a117-148b3945749f", + "user_id": "5c7bdb6eb0504d54a619acf8375c08ce", + "name": "snapshot_share1", + "created_at": "2021-06-07T11:50:39.756808", + "description": "Here is a snapshot of share Share1", + "share_proto": "NFS", + "share_size": 1, + "id": "6d221c1d-0200-461e-8d20-24b4776b9ddb", + "project_id": "cadd7139bc3148b8973df097c0911016", + "size": 1 +} + + +class TestShareSnapshot(base.TestCase): + + def test_basic(self): + snapshot_resource = share_snapshot.ShareSnapshot() + self.assertEqual( + 'snapshots', snapshot_resource.resources_key) + self.assertEqual('/snapshots', snapshot_resource.base_path) + self.assertTrue(snapshot_resource.allow_list) + + self.assertDictEqual({ + "limit": "limit", + "marker": "marker", + "snapshot_id": "snapshot_id" + }, + snapshot_resource._query_mapping._mapping) + + def test_make_share_snapshot(self): + snapshot_resource = share_snapshot.ShareSnapshot(**EXAMPLE) + self.assertEqual(EXAMPLE['id'], snapshot_resource.id) + self.assertEqual(EXAMPLE['share_id'], snapshot_resource.share_id) + self.assertEqual(EXAMPLE['user_id'], + snapshot_resource.user_id) + self.assertEqual(EXAMPLE['created_at'], snapshot_resource.created_at) + self.assertEqual(EXAMPLE['status'], snapshot_resource.status) + self.assertEqual(EXAMPLE['name'], snapshot_resource.name) + self.assertEqual( + EXAMPLE['description'], snapshot_resource.description) + self.assertEqual( + EXAMPLE['share_proto'], snapshot_resource.share_proto) + self.assertEqual( + EXAMPLE['share_size'], snapshot_resource.share_size) + self.assertEqual( + EXAMPLE['project_id'], snapshot_resource.project_id) + self.assertEqual(EXAMPLE['size'], snapshot_resource.size) diff --git a/releasenotes/notes/add-share-snapshot-to-shared-file-82ecedbdbed2e3c5.yaml b/releasenotes/notes/add-share-snapshot-to-shared-file-82ecedbdbed2e3c5.yaml new file mode 100644 index 000000000..294fddca0 --- /dev/null +++ b/releasenotes/notes/add-share-snapshot-to-shared-file-82ecedbdbed2e3c5.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support to create, update, list, get, and delete share + snapshots to shared file system service.