Resource locks and access rules restrictions

Implement resource locks and access rules restrictions feature
in the openstacksdk.

Depends-On: Ib9f65a4523222f1224d51534c5061f90501b59d3
Change-Id: I45f9b06b1b41756d34f39604c82e28fd4eb102de
This commit is contained in:
silvacarloss 2023-07-25 12:31:06 -03:00
parent 568921ce5b
commit 4e0d693816
12 changed files with 434 additions and 12 deletions

View File

@ -133,7 +133,9 @@ Shared File System Share Access Rules
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Create, View, and Delete access rules for shares from the Create, View, and Delete access rules for shares from the
Shared File Systems service. Shared File Systems service. Access rules can also have their deletion
and visibility restricted during creation. A lock reason can also be
specified. The deletion restriction can be removed during the access removal.
.. autoclass:: openstack.shared_file_system.v2._proxy.Proxy .. autoclass:: openstack.shared_file_system.v2._proxy.Proxy
:noindex: :noindex:
@ -177,3 +179,16 @@ Shared File Systems service.
:members: get_share_metadata, get_share_metadata_item, :members: get_share_metadata, get_share_metadata_item,
create_share_metadata, update_share_metadata, create_share_metadata, update_share_metadata,
delete_share_metadata delete_share_metadata
Shared File System Resource Locks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Create, list, update and delete locks for resources. When a resource is
locked, it means that it can be deleted only by services, admins or
the user that created the lock.
.. autoclass:: openstack.shared_file_system.v2._proxy.Proxy
:noindex:
:members: resource_locks, get_resource_lock, update_resource_lock,
delete_resource_lock, create_resource_lock

View File

@ -17,3 +17,4 @@ Shared File System service resources
v2/share_group v2/share_group
v2/share_access_rule v2/share_access_rule
v2/share_group_snapshot v2/share_group_snapshot
v2/resource_locks

View File

@ -0,0 +1,13 @@
openstack.shared_file_system.v2.resource_locks
==============================================
.. automodule:: openstack.shared_file_system.v2.resource_locks
The Resource Locks Class
------------------------
The ``ResourceLock`` class inherits from
:class:`~openstack.resource.Resource`.
.. autoclass:: openstack.shared_file_system.v2.resource_locks.ResourceLock
:members:

View File

@ -632,6 +632,13 @@ class Proxy(adapter.Adapter):
:returns: The result of the ``create`` :returns: The result of the ``create``
:rtype: :class:`~openstack.resource.Resource` :rtype: :class:`~openstack.resource.Resource`
""" """
# Check for attributes whose names conflict with the parameters
# specified in the method.
conflicting_attrs = attrs.get('__conflicting_attrs', {})
if conflicting_attrs:
for k, v in conflicting_attrs.items():
attrs[k] = v
attrs.pop('__conflicting_attrs')
conn = self._get_connection() conn = self._get_connection()
res = resource_type.new(connection=conn, **attrs) res = resource_type.new(connection=conn, **attrs)
return res.create(self, base_path=base_path) return res.create(self, base_path=base_path)

View File

@ -16,6 +16,7 @@ from openstack.shared_file_system.v2 import (
availability_zone as _availability_zone, availability_zone as _availability_zone,
) )
from openstack.shared_file_system.v2 import limit as _limit from openstack.shared_file_system.v2 import limit as _limit
from openstack.shared_file_system.v2 import resource_locks as _resource_locks
from openstack.shared_file_system.v2 import share as _share from openstack.shared_file_system.v2 import share as _share
from openstack.shared_file_system.v2 import share_group as _share_group from openstack.shared_file_system.v2 import share_group as _share_group
from openstack.shared_file_system.v2 import ( from openstack.shared_file_system.v2 import (
@ -56,6 +57,7 @@ class Proxy(proxy.Proxy):
"share_access_rule": _share_access_rule.ShareAccessRule, "share_access_rule": _share_access_rule.ShareAccessRule,
"share_group": _share_group.ShareGroup, "share_group": _share_group.ShareGroup,
"share_group_snapshot": _share_group_snapshot.ShareGroupSnapshot, "share_group_snapshot": _share_group_snapshot.ShareGroupSnapshot,
"resource_locks": _resource_locks.ResourceLock,
} }
def availability_zones(self): def availability_zones(self):
@ -354,7 +356,13 @@ class Proxy(proxy.Proxy):
) )
def wait_for_status( def wait_for_status(
self, res, status='active', failures=None, interval=2, wait=120 self,
res,
status='active',
failures=None,
interval=2,
wait=120,
status_attr_name='status',
): ):
"""Wait for a resource to be in a particular status. """Wait for a resource to be in a particular status.
:param res: The resource to wait on to reach the specified status. :param res: The resource to wait on to reach the specified status.
@ -367,6 +375,8 @@ class Proxy(proxy.Proxy):
checks. Default to 2. checks. Default to 2.
:param wait: Maximum number of seconds to wait before the change. :param wait: Maximum number of seconds to wait before the change.
Default to 120. Default to 120.
:param status_attr_name: name of the attribute to reach the desired
status.
:returns: The resource is returned on success. :returns: The resource is returned on success.
:raises: :class:`~openstack.exceptions.ResourceTimeout` if transition :raises: :class:`~openstack.exceptions.ResourceTimeout` if transition
to the desired status failed to occur in specified seconds. to the desired status failed to occur in specified seconds.
@ -377,7 +387,13 @@ class Proxy(proxy.Proxy):
""" """
failures = [] if failures is None else failures failures = [] if failures is None else failures
return resource.wait_for_status( return resource.wait_for_status(
self, res, status, failures, interval, wait self,
res,
status,
failures,
interval,
wait,
attribute=status_attr_name,
) )
def storage_pools(self, details=True, **query): def storage_pools(self, details=True, **query):
@ -846,17 +862,25 @@ class Proxy(proxy.Proxy):
_share_access_rule.ShareAccessRule, base_path=base_path, **attrs _share_access_rule.ShareAccessRule, base_path=base_path, **attrs
) )
def delete_access_rule(self, access_id, share_id, ignore_missing=True): def delete_access_rule(
self, access_id, share_id, ignore_missing=True, *, unrestrict=False
):
"""Deletes an access rule """Deletes an access rule
:param access_id: The id of the access rule to get :param access_id: The id of the access rule to get
:param share_id: The ID of the share :param share_id: The ID of the share
:param unrestrict: If Manila must attempt removing locks while deleting
:rtype: ``requests.models.Response`` HTTP response from internal :rtype: ``requests.models.Response`` HTTP response from internal
requests client requests client
""" """
res = self._get_resource(_share_access_rule.ShareAccessRule, access_id) res = self._get_resource(_share_access_rule.ShareAccessRule, access_id)
res.delete(self, share_id, ignore_missing=ignore_missing) return res.delete(
self,
share_id,
ignore_missing=ignore_missing,
unrestrict=unrestrict,
)
def share_group_snapshots(self, details=True, **query): def share_group_snapshots(self, details=True, **query):
"""Lists all share group snapshots. """Lists all share group snapshots.
@ -1065,3 +1089,112 @@ class Proxy(proxy.Proxy):
raise exceptions.SDKException( raise exceptions.SDKException(
"Some keys failed to be deleted %s" % keys_failed_to_delete "Some keys failed to be deleted %s" % keys_failed_to_delete
) )
def resource_locks(self, **query):
"""Lists all resource locks.
:param kwargs query: Optional query parameters to be sent to limit
the resource locks being returned. Available parameters include:
* project_id: The project ID of the user that the lock is
created for.
* user_id: The ID of a user to filter resource locks by.
* all_projects: list locks from all projects (Admin Only)
* resource_id: The ID of the resource that the locks pertain to
filter resource locks by.
* resource_action: The action prevented by the filtered resource
locks.
* resource_type: The type of the resource that the locks pertain
to filter resource locks by.
* lock_context: The lock creators context to filter locks by.
* lock_reason: The lock reason that can be used to filter resource
locks. (Inexact search is also available with lock_reason~)
* created_since: Search for the list of resources that were created
after the specified date. The date is in yyyy-mm-dd format.
* created_before: Search for the list of resources that were
created prior to the specified date. The date is in
yyyy-mm-dd format.
* limit: The maximum number of resource locks to return.
* offset: The offset to define start point of resource lock
listing.
* sort_key: The key to sort a list of shares.
* sort_dir: The direction to sort a list of shares
* with_count: Whether to show count in API response or not,
default is False. This query parameter is useful with
pagination.
:returns: A generator of manila resource locks
:rtype: :class:`~openstack.shared_file_system.v2.
resource_locks.ResourceLock`
"""
return self._list(_resource_locks.ResourceLock, **query)
def get_resource_lock(self, resource_lock):
"""Show details of a resource lock.
:param resource_lock: The ID of a resource lock or a
:class:`~openstack.shared_file_system.v2.
resource_locks.ResourceLock` instance.
:returns: Details of the identified resource lock.
:rtype: :class:`~openstack.shared_file_system.v2.
resource_locks.ResourceLock`
"""
return self._get(_resource_locks.ResourceLock, resource_lock)
def update_resource_lock(self, resource_lock, **attrs):
"""Updates details of a single resource lock.
:param resource_lock: The ID of a resource lock or a
:class:`~openstack.shared_file_system.v2.
resource_locks.ResourceLock` instance.
:param dict attrs: The attributes to update on the resource lock
:returns: the updated resource lock
:rtype: :class:`~openstack.shared_file_system.v2.
resource_locks.ResourceLock`
"""
return self._update(
_resource_locks.ResourceLock, resource_lock, **attrs
)
def delete_resource_lock(self, resource_lock, ignore_missing=True):
"""Deletes a single resource lock
:param resource_lock: The ID of a resource lock or a
:class:`~openstack.shared_file_system.v2.
resource_locks.ResourceLock` instance.
:returns: Result of the ``delete``
:rtype: ``None``
"""
return self._delete(
_resource_locks.ResourceLock,
resource_lock,
ignore_missing=ignore_missing,
)
def create_resource_lock(self, **attrs):
"""Locks a resource.
:param dict attrs: Attributes which will be used to create
a :class:`~openstack.shared_file_system.v2.
resource_locks.ResourceLock`, comprised of the properties
on the ResourceLock class. Available parameters include:
* ``resource_id``: ID of the resource to be locked.
* ``resource_type``: type of the resource (share, access_rule).
* ``resource_action``: action to be locked (delete, show).
* ``lock_reason``: reason why you're locking the resource
(Optional).
:returns: Details of the lock
:rtype: :class:`~openstack.shared_file_system.v2.
resource_locks.ResourceLock`
"""
if attrs.get('resource_type'):
# The _create method has a parameter named resource_type, which
# refers to the type of resource to be created, so we need to avoid
# a conflict of parameters we are sending to the method.
attrs['__conflicting_attrs'] = {
'resource_type': attrs.get('resource_type')
}
attrs.pop('resource_type')
return self._create(_resource_locks.ResourceLock, **attrs)

View File

@ -0,0 +1,73 @@
# 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 ResourceLock(resource.Resource):
resource_key = "resource_lock"
resources_key = "resource_locks"
base_path = "/resource-locks"
# capabilities
allow_create = True
allow_fetch = True
allow_commit = True
allow_delete = True
allow_list = True
allow_head = False
_query_mapping = resource.QueryParameters(
"project_id",
"created_since",
"created_before",
"limit",
"offset",
"id",
"resource_id",
"resource_type",
"resource_action",
"user_id",
"lock_context",
"lock_reason",
"lock_reason~",
"sort_key",
"sort_dir",
"with_count",
"all_projects",
)
# The resource was introduced in this microversion, so it is the minimum
# version to use it. Openstacksdk currently doesn't allow to set
# minimum microversions.
_max_microversion = '2.81'
#: Properties
#: The date and time stamp when the resource was created within the
#: services database.
created_at = resource.Body("created_at", type=str)
#: The date and time stamp when the resource was last modified within the
#: services database.
updated_at = resource.Body("updated_at", type=str)
#: The ID of the user that owns the lock
user_id = resource.Body("user_id", type=str)
#: The ID of the project that owns the lock.
project_id = resource.Body("project_id", type=str)
#: The type of the resource that is locked, i.e.: share, access rule.
resource_type = resource.Body("resource_type", type=str)
#: The UUID of the resource that is locked.
resource_id = resource.Body("resource_id", type=str)
#: What action is currently locked, i.e.: deletion, visibility of fields.
resource_action = resource.Body("resource_action", type=str)
#: The reason specified while the lock was being placed.
lock_reason = resource.Body("lock_reason", type=str)
#: The context that placed the lock (user, admin or service).
lock_context = resource.Body("lock_context", type=str)

View File

@ -16,7 +16,7 @@ from openstack import utils
class ShareAccessRule(resource.Resource): class ShareAccessRule(resource.Resource):
resource_key = "share_access_rule" resource_key = "access"
resources_key = "access_list" resources_key = "access_list"
base_path = "/share-access-rules" base_path = "/share-access-rules"
@ -30,7 +30,8 @@ class ShareAccessRule(resource.Resource):
_query_mapping = resource.QueryParameters("share_id") _query_mapping = resource.QueryParameters("share_id")
_max_microversion = '2.45' # Restricted access rules became available in 2.82
_max_microversion = '2.82'
#: Properties #: Properties
#: The access credential of the entity granted share access. #: The access credential of the entity granted share access.
@ -56,6 +57,12 @@ class ShareAccessRule(resource.Resource):
#: The date and time stamp when the resource was last updated within #: The date and time stamp when the resource was last updated within
#: the services database. #: the services database.
updated_at = resource.Body("updated_at", type=str) updated_at = resource.Body("updated_at", type=str)
#: Whether the visibility of some sensitive fields is restricted or not
lock_visibility = resource.Body("lock_visibility", type=bool)
#: Whether the deletion of the access rule should be restricted or not
lock_deletion = resource.Body("lock_deletion", type=bool)
#: Reason for placing the loc
lock_reason = resource.Body("lock_reason", type=bool)
def _action(self, session, body, url, action='patch', microversion=None): def _action(self, session, body, url, action='patch', microversion=None):
headers = {'Accept': ''} headers = {'Accept': ''}
@ -75,8 +82,12 @@ class ShareAccessRule(resource.Resource):
**kwargs **kwargs
) )
def delete(self, session, share_id, ignore_missing=True): def delete(
self, session, share_id, ignore_missing=True, *, unrestrict=False
):
body = {"deny_access": {"access_id": self.id}} body = {"deny_access": {"access_id": self.id}}
if unrestrict:
body['deny_access']['unrestrict'] = True
url = utils.urljoin("/shares", share_id, "action") url = utils.urljoin("/shares", share_id, "action")
response = self._action(session, body, url) response = self._action(session, body, url)
try: try:

View File

@ -22,8 +22,8 @@ class BaseSharedFileSystemTest(base.BaseFunctionalTest):
self.require_service( self.require_service(
'shared-file-system', min_microversion=self.min_microversion 'shared-file-system', min_microversion=self.min_microversion
) )
self._set_operator_cloud(shared_file_system_api_version='2.78') self._set_operator_cloud(shared_file_system_api_version='2.82')
self._set_user_cloud(shared_file_system_api_version='2.78') self._set_user_cloud(shared_file_system_api_version='2.82')
def create_share(self, **kwargs): def create_share(self, **kwargs):
share = self.user_cloud.share.create_share(**kwargs) share = self.user_cloud.share.create_share(**kwargs)
@ -75,3 +75,13 @@ class BaseSharedFileSystemTest(base.BaseFunctionalTest):
) )
self.assertIsNotNone(share_group.id) self.assertIsNotNone(share_group.id)
return share_group return share_group
def create_resource_lock(self, **kwargs):
resource_lock = self.user_cloud.share.create_resource_lock(**kwargs)
self.addCleanup(
self.user_cloud.share.delete_resource_lock,
resource_lock.id,
ignore_missing=True,
)
self.assertIsNotNone(resource_lock.id)
return resource_lock

View File

@ -0,0 +1,96 @@
# 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 resource_locks as _resource_locks
from openstack.tests.functional.shared_file_system import base
class ResourceLocksTest(base.BaseSharedFileSystemTest):
def setUp(self):
super(ResourceLocksTest, self).setUp()
self.SHARE_NAME = self.getUniqueString()
share = self.user_cloud.shared_file_system.create_share(
name=self.SHARE_NAME,
size=2,
share_type="dhss_false",
share_protocol='NFS',
description=None,
)
self.SHARE_ID = share.id
self.user_cloud.shared_file_system.wait_for_status(
share,
status='available',
failures=['error'],
interval=5,
wait=self._wait_for_timeout,
)
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.user_cloud.shared_file_system.wait_for_status(
access_rule,
status='active',
failures=['error'],
interval=5,
wait=self._wait_for_timeout,
status_attr_name='state',
)
self.assertIsNotNone(share)
self.assertIsNotNone(share.id)
self.ACCESS_ID = access_rule.id
share_lock = self.create_resource_lock(
resource_action='delete',
resource_type='share',
resource_id=self.SHARE_ID,
lock_reason='openstacksdk testing',
)
access_lock = self.create_resource_lock(
resource_action='show',
resource_type='access_rule',
resource_id=self.ACCESS_ID,
lock_reason='openstacksdk testing',
)
self.SHARE_LOCK_ID = share_lock.id
self.ACCESS_LOCK_ID = access_lock.id
def test_get(self):
share_lock = self.user_cloud.shared_file_system.get_resource_lock(
self.SHARE_LOCK_ID
)
access_lock = self.user_cloud.shared_file_system.get_resource_lock(
self.ACCESS_LOCK_ID
)
assert isinstance(share_lock, _resource_locks.ResourceLock)
assert isinstance(access_lock, _resource_locks.ResourceLock)
self.assertEqual(self.SHARE_LOCK_ID, share_lock.id)
self.assertEqual(self.ACCESS_LOCK_ID, access_lock.id)
self.assertEqual('show', access_lock.resource_action)
def test_list(self):
resource_locks = self.user_cloud.share.resource_locks()
self.assertGreater(len(list(resource_locks)), 0)
lock_attrs = (
'id',
'lock_reason',
'resource_type',
'resource_action',
'lock_context',
'created_at',
'updated_at',
)
for lock in resource_locks:
for attribute in lock_attrs:
self.assertTrue(hasattr(lock, attribute))

View File

@ -75,3 +75,16 @@ class ShareAccessRuleTest(base.BaseSharedFileSystemTest):
'metadata', 'metadata',
): ):
self.assertTrue(hasattr(rule, attribute)) self.assertTrue(hasattr(rule, attribute))
def test_create_delete_access_rule_with_locks(self):
access_rule = self.user_cloud.share.create_access_rule(
self.SHARE_ID,
access_level="rw",
access_type="ip",
access_to="203.0.113.10",
lock_deletion=True,
lock_visibility=True,
)
self.user_cloud.share.delete_access_rule(
access_rule['id'], self.SHARE_ID, unrestrict=True
)

View File

@ -14,6 +14,7 @@ from unittest import mock
from openstack.shared_file_system.v2 import _proxy from openstack.shared_file_system.v2 import _proxy
from openstack.shared_file_system.v2 import limit from openstack.shared_file_system.v2 import limit
from openstack.shared_file_system.v2 import resource_locks
from openstack.shared_file_system.v2 import share from openstack.shared_file_system.v2 import share
from openstack.shared_file_system.v2 import share_access_rule from openstack.shared_file_system.v2 import share_access_rule
from openstack.shared_file_system.v2 import share_group from openstack.shared_file_system.v2 import share_group
@ -130,7 +131,7 @@ class TestSharedFileSystemShare(TestSharedFileSystemProxy):
self.proxy.wait_for_status(mock_resource, 'ACTIVE') self.proxy.wait_for_status(mock_resource, 'ACTIVE')
mock_wait.assert_called_once_with( mock_wait.assert_called_once_with(
self.proxy, mock_resource, 'ACTIVE', [], 2, 120 self.proxy, mock_resource, 'ACTIVE', [], 2, 120, attribute='status'
) )
@ -473,8 +474,49 @@ class TestAccessRuleProxy(test_proxy_base.TestProxyBase):
"openstack.shared_file_system.v2.share_access_rule." "openstack.shared_file_system.v2.share_access_rule."
+ "ShareAccessRule.delete", + "ShareAccessRule.delete",
self.proxy.delete_access_rule, self.proxy.delete_access_rule,
method_args=['access_id', 'share_id', 'ignore_missing'], method_args=[
'access_id',
'share_id',
'ignore_missing',
],
expected_args=[self.proxy, 'share_id'], expected_args=[self.proxy, 'share_id'],
expected_kwargs={'unrestrict': False},
)
class TestResourceLocksProxy(test_proxy_base.TestProxyBase):
def setUp(self):
super(TestResourceLocksProxy, self).setUp()
self.proxy = _proxy.Proxy(self.session)
def test_list_resource_locks(self):
self.verify_list(
self.proxy.resource_locks, resource_locks.ResourceLock
)
def test_resource_lock_get(self):
self.verify_get(
self.proxy.get_resource_lock, resource_locks.ResourceLock
)
def test_resource_lock_delete(self):
self.verify_delete(
self.proxy.delete_resource_lock, resource_locks.ResourceLock, False
)
def test_resource_lock_delete_ignore(self):
self.verify_delete(
self.proxy.delete_resource_lock, resource_locks.ResourceLock, True
)
def test_resource_lock_create(self):
self.verify_create(
self.proxy.create_resource_lock, resource_locks.ResourceLock
)
def test_resource_lock_update(self):
self.verify_update(
self.proxy.update_resource_lock, resource_locks.ResourceLock
) )

View File

@ -0,0 +1,8 @@
---
features:
- |
Added support to manipulate resource locks from the shared file system
service.
- |
Added support to restrict the visibility and deletion of the shared file
system share access rules.