Add share resource to shared file system

Introduce Shares class with basic methods
including list, create, delete, get and update to
shared file system storage service.

[1] https://tree.taiga.io/project/ashrod98-openstacksdk-manila-support/us/11?kanban-status=2360120

Change-Id: Ia7d7c88eb55307296336e2f04a00e92bdca6f8ae
This commit is contained in:
Ashley Rodriguez 2021-02-15 19:32:21 +00:00 committed by Kafilat Adeleke
parent 5ffb21b9d9
commit df5f987c1e
10 changed files with 498 additions and 2 deletions

View File

@ -22,3 +22,14 @@ service.
.. autoclass:: openstack.shared_file_system.v2._proxy.Proxy
:noindex:
:members: availability_zones
Shared File System Shares
^^^^^^^^^^^^^^^^^^^^^^^^^
Interact with Shares supported by the Shared File Systems
service.
.. autoclass:: openstack.shared_file_system.v2._proxy.Proxy
:noindex:
:members: shares, get_share, delete_share, update_share, create_share

View File

@ -5,3 +5,4 @@ Shared File System service resources
:maxdepth: 1
v2/availability_zone
v2/share

View File

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

View File

@ -11,7 +11,10 @@
# under the License.
from openstack import proxy
from openstack.shared_file_system.v2 import availability_zone
from openstack import resource
from openstack.shared_file_system.v2 import (
availability_zone as _availability_zone)
from openstack.shared_file_system.v2 import share as _share
class Proxy(proxy.Proxy):
@ -23,4 +26,123 @@ class Proxy(proxy.Proxy):
:rtype: :class:`~openstack.shared_file_system.v2.
\availability_zone.AvailabilityZone`
"""
return self._list(availability_zone.AvailabilityZone)
return self._list(_availability_zone.AvailabilityZone)
def shares(self, details=True, **query):
"""Lists all shares with details
:param kwargs query: Optional query parameters to be sent to limit
the shares being returned. Available parameters include:
* status: Filters by a share status
* share_server_id: The UUID of the share server.
* metadata: One or more metadata key and value pairs as a url
encoded dictionary of strings.
* extra_specs: The extra specifications as a set of one or more
key-value pairs.
* share_type_id: The UUID of a share type to query resources by.
* name: The user defined name of the resource to filter resources
by.
* snapshot_id: The UUID of the shares base snapshot to filter
the request based on.
* host: The host name of the resource to query with.
* share_network_id: The UUID of the share network to filter
resources by.
* project_id: The ID of the project that owns the resource.
* is_public: A boolean query parameter that, when set to true,
allows retrieving public resources that belong to
all projects.
* share_group_id: The UUID of a share group to filter resource.
* export_location_id: The export location UUID that can be used
to filter shares or share instances.
* export_location_path: The export location path that can be used
to filter shares or share instances.
* name~: The name pattern that can be used to filter shares, share
snapshots, share networks or share groups.
* description~: The description pattern that can be used to filter
shares, share snapshots, share networks or share groups.
* with_count: Whether to show count in API response or not,
default is False.
* limit: The maximum number of shares to return.
* offset: The offset to define start point of share or share group
listing.
* sort_key: The key to sort a list of shares.
* sort_dir: The direction to sort a list of shares. A valid value
is asc, or desc.
:returns: Details of shares resources
:rtype: :class:`~openstack.shared_file_system.v2.
share.Share`
"""
base_path = '/shares/detail' if details else None
return self._list(_share.Share, base_path=base_path, **query)
def get_share(self, share_id):
"""Lists details of a single share
:param share: The ID of the share to get
:returns: Details of the identified share
:rtype: :class:`~openstack.shared_file_system.v2.
share.Share`
"""
return self._get(_share.Share, share_id)
def delete_share(self, share, ignore_missing=True):
"""Deletes a single share
:param share: The ID of the share to delete
:returns: Result of the ``delete``
:rtype: ``None``
"""
self._delete(_share.Share, share,
ignore_missing=ignore_missing)
def update_share(self, share_id, **attrs):
"""Updates details of a single share.
:param share: The ID of the share to update
:pram dict attrs: The attributes to update on the share
:returns: the updated share
:rtype: :class:`~openstack.shared_file_system.v2.
share.Share`
"""
return self._update(_share.Share, share_id, **attrs)
def create_share(self, **attrs):
"""Creates a share from attributes
:returns: Details of the new share
:param dict attrs: Attributes which will be used to create
a :class:`~openstack.shared_file_system.v2.
shares.Shares`,comprised of the properties
on the Shares class. 'size' and 'share'
are required to create a share.
:rtype: :class:`~openstack.shared_file_system.v2.
share.Share`
"""
return self._create(_share.Share, **attrs)
def wait_for_status(self, res, status='active', failures=None,
interval=2, wait=120):
"""Wait for a resource to be in a particular status.
:param res: The resource to wait on to reach the specified status.
The resource must have a ``status`` attribute.
:type resource: A :class:`~openstack.resource.Resource` object.
:param status: Desired status.
:param failures: Statuses that would be interpreted as failures.
:type failures: :py:class:`list`
: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 the desired status failed to occur in specified seconds.
:raises: :class:`~openstack.exceptions.ResourceFailure` if the resource
has transited to one of the failure statuses.
:raises: :class:`~AttributeError` if the resource does not have a
``status`` attribute.
"""
failures = [] if failures is None else failures
return resource.wait_for_status(
self, res, status, failures, interval, wait)

View File

@ -0,0 +1,102 @@
# 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 Share(resource.Resource):
resource_key = "share"
resources_key = "shares"
base_path = "/shares"
# capabilities
allow_create = True
allow_fetch = True
allow_commit = True
allow_delete = True
allow_list = True
allow_head = False
#: Properties
#: The share instance access rules status. A valid value is active,
#: error, or syncing.
access_rules_status = resource.Body("access_rules_status", type=str)
#: The availability zone.
availability_zone = resource.Body("availability_zone", type=str)
#: The date and time stamp when the resource was created within the
#: services database.
created_at = resource.Body("created_at", type=str)
#: The user defined description of the resource.
description = resource.Body("description", type=str)
#: The share host name.
host = resource.Body("host", type=str)
#: The level of visibility for the share.
is_public = resource.Body("is_public", type=bool)
#: Whether or not this share supports snapshots that can be
#: cloned into new shares.
is_creating_new_share_from_snapshot_supported = resource.Body(
"create_share_from_snapshot_support", type=bool)
#: Whether the share's snapshots can be mounted directly and access
#: controlled independently or not.
is_mounting_snapshot_supported = resource.Body(
"mount_snapshot_support", type=bool)
#: Whether the share can be reverted to its latest snapshot or not.
is_reverting_to_snapshot_supported = resource.Body(
"revert_to_snapshot_support", type=bool)
#: An extra specification that filters back ends by whether the share
#: supports snapshots or not.
is_snapshot_supported = resource.Body(
"snapshot_support", type=bool)
#: Indicates whether the share has replicas or not.
is_replicated = resource.Body("has_replicas", type=bool)
#: One or more metadata key and value pairs as a dictionary of strings.
metadata = resource.Body("metadata", type=dict)
#: The progress of the share creation.
progress = resource.Body("progress", type=str)
#: The ID of the project that owns the resource.
project_id = resource.Body("project_id", type=str)
#: The share replication type. Valid values are none, readable,
#: writable and dr.
replication_type = resource.Body("replication_type", type=str)
#: The UUID of the share group that this shares belongs to.
share_group_id = resource.Body("share_group_id", type=str)
#: The share network ID.
share_network_id = resource.Body("share_network_id", type=str)
#: The Shared File Systems protocol. A valid value is NFS,
#: CIFS, GlusterFS, HDFS, CephFS, MAPRFS
share_protocol = resource.Body("share_proto", type=str)
#: The UUID of the share server.
share_server_id = resource.Body("share_server_id", type=str)
#: The UUID of the share type. In minor versions, this parameter is a
#: share type name, as a string.
share_type = resource.Body("share_type", type=str)
#: Name of the share type.
share_type_name = resource.Body("share_type_name", type=str)
#: The share size, in GiBs.
size = resource.Body("size", type=int)
#: The UUID of the snapshot that was used to create the
#: share.
snapshot_id = resource.Body("snapshot_id", type=str)
#: The ID of the group snapshot instance that was used to create
#: this share.
source_share_group_snapshot_member_id = resource.Body(
"source_share_group_snapshot_member_id", type=str)
#: The share status
status = resource.Body("status", type=str)
#: For the share migration, the migration task state.
task_state = resource.Body("task_state", type=str)
#: ID of the user that the share was created by.
user_id = resource.Body("user_id", type=str)
#: Display name for updating name
display_name = resource.Body("display_name", type=str)
#: Display description for updating description
display_description = resource.Body("display_description", type=str)

View File

@ -21,3 +21,10 @@ class BaseSharedFileSystemTest(base.BaseFunctionalTest):
super(BaseSharedFileSystemTest, self).setUp()
self.require_service('shared-file-system',
min_microversion=self.min_microversion)
def create_share(self, **kwargs):
share = self.conn.share.create_share(**kwargs)
self.addCleanup(self.conn.share.delete_share, share.id,
ignore_missing=True)
self.assertIsNotNone(share.id)
return share

View File

@ -0,0 +1,60 @@
# 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 ShareTest(base.BaseSharedFileSystemTest):
def setUp(self):
super(ShareTest, self).setUp()
self.SHARE_NAME = self.getUniqueString()
my_share = self.conn.shared_file_system.create_share(
name=self.SHARE_NAME, size=2, share_type="dhss_false",
share_protocol='NFS', description=None)
self.conn.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
def tearDown(self):
sot = self.conn.shared_file_system.delete_share(
self.SHARE_ID,
ignore_missing=True)
self.assertIsNone(sot)
super(ShareTest, self).tearDown()
def test_get(self):
sot = self.conn.shared_file_system.get_share(self.SHARE_ID)
assert isinstance(sot, _share.Share)
self.assertEqual(self.SHARE_ID, sot.id)
def test_list_share(self):
shares = self.conn.shared_file_system.shares(details=False)
self.assertGreater(len(list(shares)), 0)
for share in shares:
for attribute in ('id', 'name', 'created_at', 'updated_at'):
self.assertTrue(hasattr(share, attribute))
def test_update(self):
updated_share = self.conn.shared_file_system.update_share(
self.SHARE_ID, display_description='updated share')
get_updated_share = self.conn.shared_file_system.get_share(
updated_share.id)
self.assertEqual('updated share', get_updated_share.description)

View File

@ -0,0 +1,64 @@
# 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 unittest import mock
from openstack.shared_file_system.v2 import _proxy
from openstack.shared_file_system.v2 import share
from openstack.tests.unit import test_proxy_base
class TestSharedFileSystemProxy(test_proxy_base.TestProxyBase):
def setUp(self):
super(TestSharedFileSystemProxy, self).setUp()
self.proxy = _proxy.Proxy(self.session)
def test_shares(self):
self.verify_list(self.proxy.shares, share.Share)
def test_shares_detailed(self):
self.verify_list(self.proxy.shares, share.Share,
method_kwargs={"details": True, "query": 1},
expected_kwargs={"query": 1})
def test_shares_not_detailed(self):
self.verify_list(self.proxy.shares, share.Share,
method_kwargs={"details": False, "query": 1},
expected_kwargs={"query": 1})
def test_share_get(self):
self.verify_get(self.proxy.get_share, share.Share)
def test_share_delete(self):
self.verify_delete(
self.proxy.delete_share, share.Share, False)
def test_share_delete_ignore(self):
self.verify_delete(
self.proxy.delete_share, share.Share, True)
def test_share_create(self):
self.verify_create(self.proxy.create_share, share.Share)
def test_share_update(self):
self.verify_update(self.proxy.update_share, share.Share)
@mock.patch("openstack.resource.wait_for_status")
def test_wait_for(self, mock_wait):
mock_resource = mock.Mock()
mock_wait.return_value = mock_resource
self.proxy.wait_for_status(mock_resource, 'ACTIVE')
mock_wait.assert_called_once_with(self.proxy, mock_resource,
'ACTIVE', [], 2, 120)

View File

@ -0,0 +1,111 @@
# 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
from openstack.tests.unit import base
IDENTIFIER = '08a87d37-5ca2-4308-86c5-cba06d8d796c'
EXAMPLE = {
"id": IDENTIFIER,
"size": 1,
"availability_zone": "manila-zone-1",
"created_at": "2021-02-11T17:38:00.000000",
"status": "available",
"name": None,
"description": None,
"project_id": "d19444eb73af4b37bc0794532ef6fc50",
"snapshot_id": None,
"share_network_id": None,
"share_protocol": "NFS",
"metadata": {},
"share_type": "cbb18bb7-cc97-477a-b64b-ed7c7f2a1c67",
"volume_type": "default",
"is_public": False,
"is_snapshot_supported": True,
"task_state": None,
"share_type_name": "default",
"access_rules_status": "active",
"replication_type": None,
"is_replicated": False,
"user_id": "6c262cab98de42c2afc4cfccbefc50c7",
"is_creating_new_share_from_snapshot_supported": True,
"is_reverting_to_snapshot_supported": True,
"share_group_id": None,
"source_share_group_snapshot_member_id": None,
"is_mounting_snapshot_supported": True,
"progress": "100%",
"share_server_id": None,
"host": "new@denver#lvm-single-pool"
}
class TestShares(base.TestCase):
def test_basic(self):
shares_resource = share.Share()
self.assertEqual('shares', shares_resource.resources_key)
self.assertEqual('/shares', shares_resource.base_path)
self.assertTrue(shares_resource.allow_list)
self.assertTrue(shares_resource.allow_create)
self.assertTrue(shares_resource.allow_fetch)
self.assertTrue(shares_resource.allow_commit)
self.assertTrue(shares_resource.allow_delete)
def test_make_shares(self):
shares_resource = share.Share(**EXAMPLE)
self.assertEqual(EXAMPLE['id'], shares_resource.id)
self.assertEqual(EXAMPLE['size'], shares_resource.size)
self.assertEqual(EXAMPLE['availability_zone'],
shares_resource.availability_zone)
self.assertEqual(EXAMPLE['created_at'], shares_resource.created_at)
self.assertEqual(EXAMPLE['status'], shares_resource.status)
self.assertEqual(EXAMPLE['name'], shares_resource.name)
self.assertEqual(EXAMPLE['description'],
shares_resource.description)
self.assertEqual(EXAMPLE['project_id'], shares_resource.project_id)
self.assertEqual(EXAMPLE['snapshot_id'], shares_resource.snapshot_id)
self.assertEqual(EXAMPLE['share_network_id'],
shares_resource.share_network_id)
self.assertEqual(EXAMPLE['share_protocol'],
shares_resource.share_protocol)
self.assertEqual(EXAMPLE['metadata'], shares_resource.metadata)
self.assertEqual(EXAMPLE['share_type'], shares_resource.share_type)
self.assertEqual(EXAMPLE['is_public'], shares_resource.is_public)
self.assertEqual(EXAMPLE['is_snapshot_supported'],
shares_resource.is_snapshot_supported)
self.assertEqual(EXAMPLE['task_state'], shares_resource.task_state)
self.assertEqual(EXAMPLE['share_type_name'],
shares_resource.share_type_name)
self.assertEqual(EXAMPLE['access_rules_status'],
shares_resource.access_rules_status)
self.assertEqual(EXAMPLE['replication_type'],
shares_resource.replication_type)
self.assertEqual(EXAMPLE['is_replicated'],
shares_resource.is_replicated)
self.assertEqual(EXAMPLE['user_id'], shares_resource.user_id)
self.assertEqual(EXAMPLE[
'is_creating_new_share_from_snapshot_supported'],
(shares_resource.is_creating_new_share_from_snapshot_supported))
self.assertEqual(EXAMPLE['is_reverting_to_snapshot_supported'],
shares_resource.is_reverting_to_snapshot_supported)
self.assertEqual(EXAMPLE['share_group_id'],
shares_resource.share_group_id)
self.assertEqual(EXAMPLE[
'source_share_group_snapshot_member_id'],
shares_resource.source_share_group_snapshot_member_id)
self.assertEqual(EXAMPLE['is_mounting_snapshot_supported'],
shares_resource.is_mounting_snapshot_supported)
self.assertEqual(EXAMPLE['progress'],
shares_resource.progress)
self.assertEqual(EXAMPLE['share_server_id'],
shares_resource.share_server_id)
self.assertEqual(EXAMPLE['host'], shares_resource.host)

View File

@ -0,0 +1,5 @@
---
features:
- |
Added support to create, update, list, get, and delete shares
(from shared file system service).