diff --git a/doc/source/user/proxies/shared_file_system.rst b/doc/source/user/proxies/shared_file_system.rst index 96897c408..a3cd6d128 100644 --- a/doc/source/user/proxies/shared_file_system.rst +++ b/doc/source/user/proxies/shared_file_system.rst @@ -126,3 +126,15 @@ service. :noindex: :members: share_network_subnets, get_share_network_subnet, create_share_network_subnet, delete_share_network_subnet + + +Shared File System Share Access Rules +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Create, View, and Delete access rules for shares from the +Shared File Systems service. + +.. autoclass:: openstack.shared_file_system.v2._proxy.Proxy + :noindex: + :members: access_rules, get_access_rule, create_access_rule, + delete_access_rule diff --git a/doc/source/user/resources/shared_file_system/index.rst b/doc/source/user/resources/shared_file_system/index.rst index 8c60006f7..84dd66ed8 100644 --- a/doc/source/user/resources/shared_file_system/index.rst +++ b/doc/source/user/resources/shared_file_system/index.rst @@ -14,3 +14,4 @@ Shared File System service resources v2/share_snapshot_instance v2/share_network v2/user_message + v2/share_access_rule diff --git a/doc/source/user/resources/shared_file_system/v2/share_access_rule.rst b/doc/source/user/resources/shared_file_system/v2/share_access_rule.rst new file mode 100644 index 000000000..eec6b43c0 --- /dev/null +++ b/doc/source/user/resources/shared_file_system/v2/share_access_rule.rst @@ -0,0 +1,13 @@ +openstack.shared_file_system.v2.share_access_rule +================================================= + +.. automodule:: openstack.shared_file_system.v2.share_access_rule + +The ShareAccessRule Class +------------------------- + +The ``ShareAccessRule`` class inherits from +:class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.shared_file_system.v2.share_access_rule.ShareAccessRule + :members: diff --git a/openstack/proxy.py b/openstack/proxy.py index 5519b9d9a..45d94767b 100644 --- a/openstack/proxy.py +++ b/openstack/proxy.py @@ -689,7 +689,6 @@ class Proxy(adapter.Adapter, Generic[T]): :class:`~openstack.resource.Resource` that doesn't match the ``resource_type``. """ - data = resource_type.list( self, paginated=paginated, base_path=base_path, **attrs diff --git a/openstack/shared_file_system/v2/_proxy.py b/openstack/shared_file_system/v2/_proxy.py index e9c801914..91832e54a 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_access_rule as _share_access_rule +) from openstack.shared_file_system.v2 import ( share_export_locations as _share_export_locations ) @@ -55,6 +58,7 @@ class Proxy(proxy.Proxy): "share_instance": _share_instance.ShareInstance, "share_export_locations": _share_export_locations.ShareExportLocation, + "share_access_rule": _share_access_rule.ShareAccessRule, } def availability_zones(self): @@ -628,3 +632,54 @@ class Proxy(proxy.Proxy): return self._get( _share_export_locations.ShareExportLocation, export_location_id, share_id=share_id) + + def access_rules(self, share, **query): + """Lists the access rules on a share. + + :returns: A generator of the share access rules. + :rtype: :class:`~openstack.shared_file_system.v2. + share_access_rules.ShareAccessRules` + """ + share = self._get_resource(_share.Share, share) + return self._list( + _share_access_rule.ShareAccessRule, + share_id=share.id, **query) + + def get_access_rule(self, access_id): + """List details of an access rule. + + :param access_id: The id of the access rule to get + :returns: Details of the identified access rule. + :rtype: :class:`~openstack.shared_file_system.v2. + share_access_rules.ShareAccessRules` + """ + return self._get( + _share_access_rule.ShareAccessRule, access_id) + + def create_access_rule(self, share_id, **attrs): + """Creates an access rule from attributes + + :returns: Details of the new access rule + :param share_id: The ID of the share + :param dict attrs: Attributes which will be used to create + a :class:`~openstack.shared_file_system.v2. + share_access_rules.ShareAccessRules`, comprised of the + properties on the ShareAccessRules class. + :rtype: :class:`~openstack.shared_file_system.v2. + share_access_rules.ShareAccessRules` + """ + base_path = "/shares/%s/action" % (share_id,) + return self._create( + _share_access_rule.ShareAccessRule, base_path=base_path, **attrs) + + def delete_access_rule(self, access_id, share_id, ignore_missing=True): + """Deletes an access rule + + :param access_id: The id of the access rule to get + :param share_id: The ID of the share + + :rtype: ``None`` + """ + res = self._get_resource( + _share_access_rule.ShareAccessRule, access_id) + res.delete(self, share_id, ignore_missing=ignore_missing) diff --git a/openstack/shared_file_system/v2/share_access_rule.py b/openstack/shared_file_system/v2/share_access_rule.py new file mode 100644 index 000000000..c240f9b07 --- /dev/null +++ b/openstack/shared_file_system/v2/share_access_rule.py @@ -0,0 +1,86 @@ +# 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 ShareAccessRule(resource.Resource): + resource_key = "share_access_rule" + resources_key = "access_list" + base_path = "/share-access-rules" + + # capabilities + allow_create = True + allow_fetch = True + allow_commit = False + allow_delete = True + allow_list = True + allow_head = False + + _query_mapping = resource.QueryParameters("share_id") + + #: Properties + #: The access credential of the entity granted share access. + access_key = resource.Body("access_key", type=str) + #: The access level to the share. + access_level = resource.Body("access_level", type=str) + #: The object of the access rule. + access_list = resource.Body("access_list", type=str) + #: The value that defines the access. + access_to = resource.Body("access_to", type=str) + #: The access rule type. + access_type = resource.Body("access_type", type=str) + #: The date and time stamp when the resource was created within the + #: service’s database. + created_at = resource.Body("created_at", type=str) + #: One or more access rule metadata key and value pairs as a dictionary + #: of strings. + metadata = resource.Body("metadata", type=dict) + #: The UUID of the share to which you are granted or denied access. + share_id = resource.Body("share_id", type=str) + #: The state of the access rule. + state = resource.Body("state", type=str) + #: The date and time stamp when the resource was last updated within + #: the service’s database. + updated_at = resource.Body("updated_at", type=str) + + def _action(self, session, body, url, + action='patch', microversion=None): + headers = {'Accept': ''} + + if microversion is None: + microversion = \ + self._get_microversion(session, action=action) + + session.post( + url, + json=body, + headers=headers, + microversion=microversion) + + def create(self, session, **kwargs): + return super().create(session, + resource_request_key='allow_access', + resource_response_key='access', + **kwargs) + + def delete(self, session, share_id, ignore_missing=True): + body = {'deny_access': {'access_id' : self.id}} + url = utils.urljoin('/shares', share_id, 'action') + try: + response = self._action(session, body, url) + except exceptions.ResourceNotFound: + if not ignore_missing: + raise + return response diff --git a/openstack/tests/functional/shared_file_system/test_share_access_rule.py b/openstack/tests/functional/shared_file_system/test_share_access_rule.py new file mode 100644 index 000000000..b5e56d907 --- /dev/null +++ b/openstack/tests/functional/shared_file_system/test_share_access_rule.py @@ -0,0 +1,70 @@ +# 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 ShareAccessRuleTest(base.BaseSharedFileSystemTest): + + def setUp(self): + super(ShareAccessRuleTest, self).setUp() + + self.SHARE_NAME = self.getUniqueString() + mys = self.create_share( + name=self.SHARE_NAME, size=2, share_type="dhss_false", + share_protocol='NFS', description=None) + self.user_cloud.shared_file_system.wait_for_status( + mys, + status='available', + failures=['error'], + interval=5, + wait=self._wait_for_timeout) + self.assertIsNotNone(mys) + self.assertIsNotNone(mys.id) + self.SHARE_ID = mys.id + self.SHARE = mys + access_rule = self.user_cloud.share.create_access_rule( + self.SHARE_ID, + access_level="rw", + access_type="ip", + access_to="0.0.0.0/0" + ) + self.ACCESS_ID = access_rule.id + self.RESOURCE_KEY = access_rule.resource_key + + def tearDown(self): + acr = self.user_cloud.share.delete_access_rule( + self.ACCESS_ID, + self.SHARE_ID, + ignore_missing=True + ) + + self.assertIsNone(acr) + super(ShareAccessRuleTest, self).tearDown() + + def test_get_access_rule(self): + sot = self.user_cloud.shared_file_system.get_access_rule( + self.ACCESS_ID + ) + self.assertEqual(self.ACCESS_ID, sot.id) + + def test_list_access_rules(self): + rules = self.user_cloud.shared_file_system.access_rules( + self.SHARE, + details=True + ) + self.assertGreater(len(list(rules)), 0) + for rule in rules: + for attribute in ('id', 'created_at', 'updated_at', + 'access_level', 'access_type', 'access_to', + 'share_id', 'access_key', 'metadata'): + self.assertTrue(hasattr(rule, attribute)) 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 9748866ce..cb3a1bd0f 100644 --- a/openstack/tests/unit/shared_file_system/v2/test_proxy.py +++ b/openstack/tests/unit/shared_file_system/v2/test_proxy.py @@ -12,6 +12,9 @@ from unittest import mock +from openstack.shared_file_system.v2 import ( + share_access_rule +) from openstack.shared_file_system.v2 import _proxy from openstack.shared_file_system.v2 import limit from openstack.shared_file_system.v2 import share @@ -343,3 +346,37 @@ class TestShareNetworkSubnetResource(test_proxy_base.TestProxyBase): method_args=["fake_network_id", "fake_sub_network_id"], expected_args=["fake_sub_network_id"], expected_kwargs={'share_network_id': 'fake_network_id'}) + + +class TestAccessRuleProxy(test_proxy_base.TestProxyBase): + + def setUp(self): + super(TestAccessRuleProxy, self).setUp() + self.proxy = _proxy.Proxy(self.session) + + def test_access_ruless(self): + self.verify_list( + self.proxy.access_rules, + share_access_rule.ShareAccessRule, + method_args=["test_share"], + expected_args=[], + expected_kwargs={"share_id": "test_share"}) + + def test_access_rules_get(self): + self.verify_get( + self.proxy.get_access_rule, share_access_rule.ShareAccessRule) + + def test_access_rules_create(self): + self.verify_create( + self.proxy.create_access_rule, + share_access_rule.ShareAccessRule, + method_args=["share_id"], + expected_args=[]) + + def test_access_rules_delete(self): + self._verify( + "openstack.shared_file_system.v2.share_access_rule." + + "ShareAccessRule.delete", + self.proxy.delete_access_rule, + method_args=['access_id', 'share_id', 'ignore_missing'], + expected_args=[self.proxy , 'share_id']) diff --git a/openstack/tests/unit/shared_file_system/v2/test_share_access_rule.py b/openstack/tests/unit/shared_file_system/v2/test_share_access_rule.py new file mode 100644 index 000000000..83762db54 --- /dev/null +++ b/openstack/tests/unit/shared_file_system/v2/test_share_access_rule.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.shared_file_system.v2 import share_access_rule +from openstack.tests.unit import base + +EXAMPLE = { + "access_level": "rw", + "state": "error", + "id": "507bf114-36f2-4f56-8cf4-857985ca87c1", + "share_id": "fb213952-2352-41b4-ad7b-2c4c69d13eef", + "access_type": "cert", + "access_to": "example.com", + "access_key": None, + "created_at": "2021-09-12T02:01:04.000000", + "updated_at": "2021-09-12T02:01:04.000000", + "metadata": { + "key1": "value1", + "key2": "value2"} +} + + +class TestShareAccessRule(base.TestCase): + + def test_basic(self): + rules_resource = share_access_rule.ShareAccessRule() + self.assertEqual('access_list', rules_resource.resources_key) + self.assertEqual('/share-access-rules', rules_resource.base_path) + self.assertTrue(rules_resource.allow_list) + + self.assertDictEqual({ + "limit": "limit", + "marker": "marker", + "share_id": "share_id" + }, + rules_resource._query_mapping._mapping) + + def test_make_share_access_rules(self): + rules_resource = share_access_rule.ShareAccessRule(**EXAMPLE) + self.assertEqual(EXAMPLE['id'], rules_resource.id) + self.assertEqual(EXAMPLE['access_level'], rules_resource.access_level) + self.assertEqual(EXAMPLE['state'], rules_resource.state) + self.assertEqual(EXAMPLE['id'], rules_resource.id) + self.assertEqual(EXAMPLE['access_type'], rules_resource.access_type) + self.assertEqual(EXAMPLE['access_to'], rules_resource.access_to) + self.assertEqual(EXAMPLE['access_key'], rules_resource.access_key) + self.assertEqual(EXAMPLE['created_at'], rules_resource.created_at) + self.assertEqual(EXAMPLE['updated_at'], rules_resource.updated_at) + self.assertEqual(EXAMPLE['metadata'], rules_resource.metadata) diff --git a/releasenotes/notes/add-share-access-rules-to-shared-file-362bee34f7331186.yaml b/releasenotes/notes/add-share-access-rules-to-shared-file-362bee34f7331186.yaml new file mode 100644 index 000000000..005de8728 --- /dev/null +++ b/releasenotes/notes/add-share-access-rules-to-shared-file-362bee34f7331186.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added support to create, list, get and delete share access rules with the + shared file system service.