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
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
:noindex:
@ -177,3 +179,16 @@ Shared File Systems service.
:members: get_share_metadata, get_share_metadata_item,
create_share_metadata, update_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_access_rule
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``
: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()
res = resource_type.new(connection=conn, **attrs)
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,
)
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_group as _share_group
from openstack.shared_file_system.v2 import (
@ -56,6 +57,7 @@ class Proxy(proxy.Proxy):
"share_access_rule": _share_access_rule.ShareAccessRule,
"share_group": _share_group.ShareGroup,
"share_group_snapshot": _share_group_snapshot.ShareGroupSnapshot,
"resource_locks": _resource_locks.ResourceLock,
}
def availability_zones(self):
@ -354,7 +356,13 @@ class Proxy(proxy.Proxy):
)
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.
:param res: The resource to wait on to reach the specified status.
@ -367,6 +375,8 @@ class Proxy(proxy.Proxy):
checks. Default to 2.
:param wait: Maximum number of seconds to wait before the change.
Default to 120.
:param status_attr_name: name of the attribute to reach the desired
status.
:returns: The resource is returned on success.
:raises: :class:`~openstack.exceptions.ResourceTimeout` if transition
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
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):
@ -846,17 +862,25 @@ class Proxy(proxy.Proxy):
_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
:param access_id: The id of the access rule to get
: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
requests client
"""
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):
"""Lists all share group snapshots.
@ -1065,3 +1089,112 @@ class Proxy(proxy.Proxy):
raise exceptions.SDKException(
"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):
resource_key = "share_access_rule"
resource_key = "access"
resources_key = "access_list"
base_path = "/share-access-rules"
@ -30,7 +30,8 @@ class ShareAccessRule(resource.Resource):
_query_mapping = resource.QueryParameters("share_id")
_max_microversion = '2.45'
# Restricted access rules became available in 2.82
_max_microversion = '2.82'
#: Properties
#: 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 services database.
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):
headers = {'Accept': ''}
@ -75,8 +82,12 @@ class ShareAccessRule(resource.Resource):
**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}}
if unrestrict:
body['deny_access']['unrestrict'] = True
url = utils.urljoin("/shares", share_id, "action")
response = self._action(session, body, url)
try:

View File

@ -22,8 +22,8 @@ class BaseSharedFileSystemTest(base.BaseFunctionalTest):
self.require_service(
'shared-file-system', min_microversion=self.min_microversion
)
self._set_operator_cloud(shared_file_system_api_version='2.78')
self._set_user_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.82')
def create_share(self, **kwargs):
share = self.user_cloud.share.create_share(**kwargs)
@ -75,3 +75,13 @@ class BaseSharedFileSystemTest(base.BaseFunctionalTest):
)
self.assertIsNotNone(share_group.id)
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',
):
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 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_access_rule
from openstack.shared_file_system.v2 import share_group
@ -130,7 +131,7 @@ class TestSharedFileSystemShare(TestSharedFileSystemProxy):
self.proxy.wait_for_status(mock_resource, 'ACTIVE')
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."
+ "ShareAccessRule.delete",
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_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.