From 6ef2af39d5cec38db3dca075aa874c5e6f40d6a8 Mon Sep 17 00:00:00 2001 From: Anvi Joshi Date: Fri, 23 Jun 2023 20:02:44 +0000 Subject: [PATCH] Implemented methods for share metadata Change-Id: Ib3c27ec3d0eb102b30a80994d6cc2af4ee78f3dd --- doc/source/user/guides/shared_file_system.rst | 46 +++++++ .../user/proxies/shared_file_system.rst | 13 ++ examples/shared_file_system/share_metadata.py | 61 +++++++++ openstack/shared_file_system/v2/_proxy.py | 92 +++++++++++++- openstack/shared_file_system/v2/share.py | 3 +- .../shared_file_system/test_share_metadata.py | 120 ++++++++++++++++++ .../unit/shared_file_system/v2/test_proxy.py | 57 +++++++++ ...ystem-share-metadata-e0415bb71d8a0a48.yaml | 6 + 8 files changed, 395 insertions(+), 3 deletions(-) create mode 100644 examples/shared_file_system/share_metadata.py create mode 100644 openstack/tests/functional/shared_file_system/test_share_metadata.py create mode 100644 releasenotes/notes/add-shared-file-system-share-metadata-e0415bb71d8a0a48.yaml diff --git a/doc/source/user/guides/shared_file_system.rst b/doc/source/user/guides/shared_file_system.rst index 938e75fc5..a2e300869 100644 --- a/doc/source/user/guides/shared_file_system.rst +++ b/doc/source/user/guides/shared_file_system.rst @@ -134,3 +134,49 @@ Deletes a share group snapshot. .. literalinclude:: ../examples/shared_file_system/share_group_snapshots.py :pyobject: delete_share_group_snapshot + + +List Share Metadata +-------------------- + +Lists all metadata for a given share. + +.. literalinclude:: ../examples/shared_file_system/share_metadata.py + :pyobject: list_share_metadata + + +Get Share Metadata Item +----------------------- + +Retrieves a specific metadata item from a shares metadata by its key. + +.. literalinclude:: ../examples/shared_file_system/share_metadata.py + :pyobject: get_share_metadata_item + + +Create Share Metadata +---------------------- + +Creates share metadata. + +.. literalinclude:: ../examples/shared_file_system/share_metadata.py + :pyobject: create_share_metadata + + +Update Share Metadata +---------------------- + +Updates metadata of a given share. + +.. literalinclude:: ../examples/shared_file_system/share_metadata.py + :pyobject: update_share_metadata + + +Delete Share Metadata +---------------------- + +Deletes a specific metadata item from a shares metadata by its key. Can +specify multiple keys to be deleted. + +.. literalinclude:: ../examples/shared_file_system/share_metadata.py + :pyobject: delete_share_metadata diff --git a/doc/source/user/proxies/shared_file_system.rst b/doc/source/user/proxies/shared_file_system.rst index 28a4ca50a..5cdaab1af 100644 --- a/doc/source/user/proxies/shared_file_system.rst +++ b/doc/source/user/proxies/shared_file_system.rst @@ -163,3 +163,16 @@ service. :members: share_group_snapshots, get_share_group_snapshot, create_share_group_snapshot, reset_share_group_snapshot_status, update_share_group_snapshot, delete_share_group_snapshot + + +Shared File System Share Metadata +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +List, Get, Create, Update, and Delete metadata for shares from the +Shared File Systems service. + +.. autoclass:: openstack.shared_file_system.v2._proxy.Proxy + :noindex: + :members: get_share_metadata, get_share_metadata_item, + create_share_metadata, update_share_metadata, + delete_share_metadata diff --git a/examples/shared_file_system/share_metadata.py b/examples/shared_file_system/share_metadata.py new file mode 100644 index 000000000..b1ca00c62 --- /dev/null +++ b/examples/shared_file_system/share_metadata.py @@ -0,0 +1,61 @@ +# 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. + + +def list_share_metadata(conn, share_id): + # Method returns the entire share with the metadata inside it. + returned_share = conn.get_share_metadata(share_id) + + # Access metadata of share + metadata = returned_share['metadata'] + + print("List All Share Metadata:") + for meta_key in metadata: + print(f"{meta_key}={metadata[meta_key]}") + + +def get_share_metadata_item(conn, share_id, key): + # Method returns the entire share with the metadata inside it. + returned_share = conn.get_share_metadata_item(share_id, key) + + # Access metadata of share + metadata = returned_share['metadata'] + + print("Get share metadata item given item key and share id:") + print(metadata[key]) + + +def create_share_metadata(conn, share_id, metadata): + # Method returns the entire share with the metadata inside it. + created_share = conn.create_share_metadata(share_id, metadata) + + # Access metadata of share + metadata = created_share['metadata'] + + print("Metadata created for given share:") + print(metadata) + + +def update_share_metadata(conn, share_id, metadata): + # Method returns the entire share with the metadata inside it. + updated_share = conn.update_share_metadata(share_id, metadata, True) + + # Access metadata of share + metadata = updated_share['metadata'] + + print("Updated metadata for given share:") + print(metadata) + + +def delete_share_metadata(conn, share_id, keys): + # Method doesn't return anything. + conn.delete_share_metadata(share_id, keys) diff --git a/openstack/shared_file_system/v2/_proxy.py b/openstack/shared_file_system/v2/_proxy.py index 4392c798f..bbe756349 100644 --- a/openstack/shared_file_system/v2/_proxy.py +++ b/openstack/shared_file_system/v2/_proxy.py @@ -9,7 +9,7 @@ # 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 proxy from openstack import resource from openstack.shared_file_system.v2 import ( @@ -820,7 +820,7 @@ class Proxy(proxy.Proxy): requests client """ res = self._get_resource(_share_access_rule.ShareAccessRule, access_id) - return res.delete(self, share_id, ignore_missing=ignore_missing) + res.delete(self, share_id, ignore_missing=ignore_missing) def share_group_snapshots(self, details=True, **query): """Lists all share group snapshots. @@ -940,3 +940,91 @@ class Proxy(proxy.Proxy): group_snapshot_id, ignore_missing=ignore_missing, ) + + # ========= Share Metadata ========== + def get_share_metadata(self, share_id): + """Lists all metadata for a share. + + :param share_id: The ID of the share + + :returns: A :class:`~openstack.shared_file_system.v2.share.Share` + with the share's metadata. + :rtype: + :class:`~openstack.shared_file_system.v2.share.Share` + """ + share = self._get_resource(_share.Share, share_id) + return share.fetch_metadata(self) + + def get_share_metadata_item(self, share_id, key): + """Retrieves a specific metadata item from a share by its key. + + :param share_id: The ID of the share + :param key: The key of the share metadata + + :returns: A :class:`~openstack.shared_file_system.v2.share.Share` + with the share's metadata. + :rtype: + :class:`~openstack.shared_file_system.v2.share.Share` + """ + share = self._get_resource(_share.Share, share_id) + return share.get_metadata_item(self, key) + + def create_share_metadata(self, share_id, **metadata): + """Creates share metadata as key-value pairs. + + :param share_id: The ID of the share + :param metadata: The metadata to be created + + :returns: A :class:`~openstack.shared_file_system.v2.share.Share` + with the share's metadata. + :rtype: + :class:`~openstack.shared_file_system.v2.share.Share` + """ + share = self._get_resource(_share.Share, share_id) + return share.set_metadata(self, metadata=metadata) + + def update_share_metadata(self, share_id, metadata, replace=False): + """Updates metadata of given share. + + :param share_id: The ID of the share + :param metadata: The metadata to be created + :param replace: Boolean for whether the preexisting metadata + should be replaced + + :returns: A :class:`~openstack.shared_file_system.v2.share.Share` + with the share's updated metadata. + :rtype: + :class:`~openstack.shared_file_system.v2.share.Share` + """ + share = self._get_resource(_share.Share, share_id) + return share.set_metadata(self, metadata=metadata, replace=replace) + + def delete_share_metadata(self, share_id, keys, ignore_missing=True): + """Deletes a single metadata item on a share, idetified by its key. + + :param share_id: The ID of the share + :param keys: The list of share metadata keys to be deleted + :param ignore_missing: Boolean indicating if missing keys should be ignored. + + :returns: None + :rtype: None + """ + share = self._get_resource(_share.Share, share_id) + keys_failed_to_delete = [] + for key in keys: + try: + share.delete_metadata_item(self, key) + except exceptions.NotFoundException: + if not ignore_missing: + self._connection.log.info("Key %s not found.", key) + keys_failed_to_delete.append(key) + except exceptions.ForbiddenException: + self._connection.log.info("Key %s cannot be deleted.", key) + keys_failed_to_delete.append(key) + except exceptions.SDKException: + self._connection.log.info("Failed to delete key %s.", key) + keys_failed_to_delete.append(key) + if keys_failed_to_delete: + raise exceptions.SDKException( + "Some keys failed to be deleted %s" % keys_failed_to_delete + ) diff --git a/openstack/shared_file_system/v2/share.py b/openstack/shared_file_system/v2/share.py index 79f9c3fa4..ebe3adfb4 100644 --- a/openstack/shared_file_system/v2/share.py +++ b/openstack/shared_file_system/v2/share.py @@ -10,12 +10,13 @@ # License for the specific language governing permissions and limitations # under the License. +from openstack.common import metadata from openstack import exceptions from openstack import resource from openstack import utils -class Share(resource.Resource): +class Share(resource.Resource, metadata.MetadataMixin): resource_key = "share" resources_key = "shares" base_path = "/shares" diff --git a/openstack/tests/functional/shared_file_system/test_share_metadata.py b/openstack/tests/functional/shared_file_system/test_share_metadata.py new file mode 100644 index 000000000..1afa56b9c --- /dev/null +++ b/openstack/tests/functional/shared_file_system/test_share_metadata.py @@ -0,0 +1,120 @@ +# 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 as _share +from openstack.tests.functional.shared_file_system import base + + +class ShareMetadataTest(base.BaseSharedFileSystemTest): + def setUp(self): + super().setUp() + + self.SHARE_NAME = self.getUniqueString() + my_share = self.create_share( + name=self.SHARE_NAME, + size=2, + share_type="dhss_false", + share_protocol='NFS', + description=None, + ) + self.SHARE_ID = my_share.id + self.assertIsNotNone(my_share) + self.assertIsNotNone(my_share.id) + + def test_create(self): + meta = {"foo": "bar"} + created_share = ( + self.user_cloud.shared_file_system.create_share_metadata( + self.SHARE_ID, **meta + ) + ) + assert isinstance(created_share, _share.Share) + self.assertEqual(created_share['metadata'], meta) + + def test_get_item(self): + meta = {"foo": "bar"} + created_share = ( + self.user_cloud.shared_file_system.create_share_metadata( + self.SHARE_ID, **meta + ) + ) + returned_share = ( + self.user_cloud.shared_file_system.get_share_metadata_item( + self.SHARE_ID, "foo" + ) + ) + self.assertEqual( + created_share['metadata']['foo'], returned_share['metadata']['foo'] + ) + + def test_get(self): + meta = {"foo": "bar"} + created_share = ( + self.user_cloud.shared_file_system.create_share_metadata( + self.SHARE_ID, **meta + ) + ) + returned_share = self.user_cloud.shared_file_system.get_share_metadata( + self.SHARE_ID + ) + self.assertEqual( + created_share['metadata']['foo'], returned_share['metadata']['foo'] + ) + + def test_update(self): + meta = {"foo": "bar"} + created_share = ( + self.user_cloud.shared_file_system.create_share_metadata( + self.SHARE_ID, **meta + ) + ) + + new_meta = {"newFoo": "newBar"} + full_meta = {"foo": "bar", "newFoo": "newBar"} + empty_meta = {} + + updated_share = ( + self.user_cloud.shared_file_system.update_share_metadata( + created_share, new_meta + ) + ) + self.assertEqual(updated_share['metadata'], new_meta) + + full_metadata = self.user_cloud.shared_file_system.get_share_metadata( + created_share + )['metadata'] + self.assertEqual(full_metadata, full_meta) + + share_with_deleted_metadata = ( + self.user_cloud.shared_file_system.update_share_metadata( + updated_share, empty_meta + ) + ) + self.assertEqual(share_with_deleted_metadata['metadata'], empty_meta) + + def test_delete(self): + meta = {"foo": "bar", "newFoo": "newBar"} + created_share = ( + self.user_cloud.shared_file_system.create_share_metadata( + self.SHARE_ID, **meta + ) + ) + + self.user_cloud.shared_file_system.delete_share_metadata( + created_share, ["foo", "invalidKey"] + ) + + deleted_share = self.user_cloud.shared_file_system.get_share_metadata( + self.SHARE_ID + ) + + self.assertEqual(deleted_share['metadata'], {"newFoo": "newBar"}) 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 795b35b44..921318f64 100644 --- a/openstack/tests/unit/shared_file_system/v2/test_proxy.py +++ b/openstack/tests/unit/shared_file_system/v2/test_proxy.py @@ -155,6 +155,63 @@ class TestSharedFileSystemStoragePool(TestSharedFileSystemProxy): ) +class TestSharedFileSystemShareMetadata(TestSharedFileSystemProxy): + def test_get_share_metadata(self): + self._verify( + "openstack.shared_file_system.v2.share.Share.fetch_metadata", + self.proxy.get_share_metadata, + method_args=["share_id"], + expected_args=[self.proxy], + expected_result=share.Share( + id="share_id", metadata={"key": "value"} + ), + ) + + def test_get_share_metadata_item(self): + self._verify( + "openstack.shared_file_system.v2.share.Share.get_metadata_item", + self.proxy.get_share_metadata_item, + method_args=["share_id", "key"], + expected_args=[self.proxy, "key"], + expected_result=share.Share( + id="share_id", metadata={"key": "value"} + ), + ) + + def test_create_share_metadata(self): + metadata = {"foo": "bar", "newFoo": "newBar"} + self._verify( + "openstack.shared_file_system.v2.share.Share.set_metadata", + self.proxy.create_share_metadata, + method_args=["share_id"], + method_kwargs=metadata, + expected_args=[self.proxy], + expected_kwargs={"metadata": metadata}, + expected_result=share.Share(id="share_id", metadata=metadata), + ) + + def test_update_share_metadata(self): + metadata = {"foo": "bar", "newFoo": "newBar"} + replace = True + self._verify( + "openstack.shared_file_system.v2.share.Share.set_metadata", + self.proxy.update_share_metadata, + method_args=["share_id", metadata, replace], + expected_args=[self.proxy], + expected_kwargs={"metadata": metadata, "replace": replace}, + expected_result=share.Share(id="share_id", metadata=metadata), + ) + + def test_delete_share_metadata(self): + self._verify( + "openstack.shared_file_system.v2.share.Share.delete_metadata_item", + self.proxy.delete_share_metadata, + expected_result=None, + method_args=["share_id", ["key"]], + expected_args=[self.proxy, "key"], + ) + + class TestUserMessageProxy(test_proxy_base.TestProxyBase): def setUp(self): super(TestUserMessageProxy, self).setUp() diff --git a/releasenotes/notes/add-shared-file-system-share-metadata-e0415bb71d8a0a48.yaml b/releasenotes/notes/add-shared-file-system-share-metadata-e0415bb71d8a0a48.yaml new file mode 100644 index 000000000..6461ec7b7 --- /dev/null +++ b/releasenotes/notes/add-shared-file-system-share-metadata-e0415bb71d8a0a48.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added support to list, get, create, + update, and delete share metadata + from shared file system service. \ No newline at end of file