diff --git a/doc/source/user/guides/shared_file_system.rst b/doc/source/user/guides/shared_file_system.rst index 40ffc3f2a..1aee6a36f 100644 --- a/doc/source/user/guides/shared_file_system.rst +++ b/doc/source/user/guides/shared_file_system.rst @@ -20,3 +20,40 @@ of the share in other availability zones. .. literalinclude:: ../examples/shared_file_system/availability_zones.py :pyobject: list_availability_zones + + +Share Instances +--------------- + +Administrators can list, show information for, explicitly set the state of, +and force-delete share instances. + +.. literalinclude:: ../examples/shared_file_system/share_instances.py + :pyobject: share_instances + + +Get Share Instance +------------------ + +Shows details for a single share instance. + +.. literalinclude:: ../examples/shared_file_system/share_instances.py + :pyobject: get_share_instance + + +Reset Share Instance Status +--------------------------- + +Explicitly updates the state of a share instance. + +.. literalinclude:: ../examples/shared_file_system/share_instances.py + :pyobject: reset_share_instance_status + + +Delete Share Instance +--------------------- + +Force-deletes a share instance. + +.. literalinclude:: ../examples/shared_file_system/share_instances.py + :pyobject: delete_share_instance diff --git a/doc/source/user/proxies/shared_file_system.rst b/doc/source/user/proxies/shared_file_system.rst index e5c5cddd4..6d4a34ac9 100644 --- a/doc/source/user/proxies/shared_file_system.rst +++ b/doc/source/user/proxies/shared_file_system.rst @@ -100,3 +100,16 @@ Create and manipulate Share Networks with the Shared File Systems service. :noindex: :members: share_networks, get_share_network, delete_share_network, update_share_network, create_share_network + +Shared File System Share Instances +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Administrators can list, show information for, explicitly set the +state of, and force-delete share instances within the Shared File +Systems Service. + +.. autoclass:: openstack.shared_file_system.v2._proxy.Proxy + :noindex: + :members: share_instances, get_share_instance, + reset_share_instance_status, + delete_share_instance diff --git a/doc/source/user/resources/shared_file_system/index.rst b/doc/source/user/resources/shared_file_system/index.rst index 3ae1cead5..56d40c533 100644 --- a/doc/source/user/resources/shared_file_system/index.rst +++ b/doc/source/user/resources/shared_file_system/index.rst @@ -8,7 +8,8 @@ Shared File System service resources v2/storage_pool v2/limit v2/share - v2/user_message + v2/share_instance v2/share_snapshot v2/share_snapshot_instance v2/share_network + v2/user_message diff --git a/doc/source/user/resources/shared_file_system/v2/share_instance.rst b/doc/source/user/resources/shared_file_system/v2/share_instance.rst new file mode 100644 index 000000000..0b058335b --- /dev/null +++ b/doc/source/user/resources/shared_file_system/v2/share_instance.rst @@ -0,0 +1,13 @@ +openstack.shared_file_system.v2.share_instance +============================================== + +.. automodule:: openstack.shared_file_system.v2.share_instance + +The ShareInstance Class +----------------------- + +The ``ShareInstance`` class inherits from +:class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.shared_file_system.v2.share_instance.ShareInstance + :members: diff --git a/examples/shared_file_system/share_instances.py b/examples/shared_file_system/share_instances.py new file mode 100644 index 000000000..93a6cbf0d --- /dev/null +++ b/examples/shared_file_system/share_instances.py @@ -0,0 +1,41 @@ +# 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. + +""" +List resources from the Shared File System service. + +For a full guide see +https://docs.openstack.org/openstacksdk/latest/user/guides/shared_file_system.html +""" + + +def share_instances(conn, **query): + print('List all share instances:') + for si in conn.share.share_instances(**query): + print(si) + + +def get_share_instance(conn, share_instance_id): + print('Get share instance with given Id:') + share_instance = conn.share.get_share_instance(share_instance_id) + print(share_instance) + + +def reset_share_instance_status(conn, share_instance_id, status): + print('Reset the status of the share instance with the given ' + 'share_instance_id to the given status') + conn.share.reset_share_instance_status(share_instance_id, status) + + +def delete_share_instance(conn, share_instance_id): + print('Force-delete the share instance with the given share_instance_id') + conn.share.delete_share_instance(share_instance_id) diff --git a/openstack/shared_file_system/v2/_proxy.py b/openstack/shared_file_system/v2/_proxy.py index 33307dab5..eae2b0994 100644 --- a/openstack/shared_file_system/v2/_proxy.py +++ b/openstack/shared_file_system/v2/_proxy.py @@ -31,6 +31,7 @@ from openstack.shared_file_system.v2 import ( ) from openstack.shared_file_system.v2 import limit as _limit from openstack.shared_file_system.v2 import share as _share +from openstack.shared_file_system.v2 import share_instance as _share_instance class Proxy(proxy.Proxy): @@ -44,6 +45,7 @@ class Proxy(proxy.Proxy): "share_network": _share_network.ShareNetwork, "share_snapshot_instance": _share_snapshot_instance.ShareSnapshotInstance, + "share_instance": _share_instance.ShareInstance, } def availability_zones(self): @@ -435,3 +437,55 @@ class Proxy(proxy.Proxy): share_network.ShareNetwork` """ return self._create(_share_network.ShareNetwork, **attrs) + + def share_instances(self, **query): + """Lists all share instances. + + :param kwargs query: Optional query parameters to be sent to limit + the share instances being returned. Available parameters include: + + * export_location_id: The export location UUID that can be used + to filter share instances. + * export_location_path: The export location path that can be used + to filter share instances. + + :returns: Details of share instances resources + :rtype: :class:`~openstack.shared_file_system.v2. + share_instance.ShareInstance` + """ + return self._list( + _share_instance.ShareInstance, **query) + + def get_share_instance(self, share_instance_id): + """Shows details for a single share instance + + :param share_instance_id: The UUID of the share instance to get + + :returns: Details of the identified share instance + :rtype: :class:`~openstack.shared_file_system.v2. + share_instance.ShareInstance` + """ + return self._get(_share_instance.ShareInstance, share_instance_id) + + def reset_share_instance_status(self, share_instance_id, status): + """Explicitly updates the state of a share instance. + + :param share_instance_id: The UUID of the share instance to reset. + :param status: The share or share instance status to be set. + + :returns: ``None`` + """ + res = self._get_resource(_share_instance.ShareInstance, + share_instance_id) + res.reset_status(self, status) + + def delete_share_instance(self, share_instance_id): + """Force-deletes a share instance + + :param share_instance: The ID of the share instance to delete + + :returns: ``None`` + """ + res = self._get_resource(_share_instance.ShareInstance, + share_instance_id) + res.force_delete(self) diff --git a/openstack/shared_file_system/v2/share_instance.py b/openstack/shared_file_system/v2/share_instance.py new file mode 100644 index 000000000..d2873a18f --- /dev/null +++ b/openstack/shared_file_system/v2/share_instance.py @@ -0,0 +1,83 @@ +# 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 ShareInstance(resource.Resource): + resource_key = "share_instance" + resources_key = "share_instances" + base_path = "/share_instances" + + # capabilities + allow_create = False + allow_fetch = True + allow_commit = False + allow_delete = False + 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 name of the availability zone the share exists within. + availability_zone = resource.Body("availability_zone", type=str) + #: If the share instance has its cast_rules_to_readonly attribute + #: set to True, all existing access rules be cast to read/only. + cast_rules_to_readonly = resource.Body("cast_rules_to_readonly", type=bool) + #: The date and time stamp when the resource was created within the + #: service’s database. + created_at = resource.Body("created_at", type=str) + #: The host name of the service back end that the resource is + #: contained within. + host = resource.Body("host", type=str) + #: The progress of the share creation. + progress = resource.Body("progress", type=str) + #: The share replica state. Has set value only when replication is used. + #: List of possible values: active, in_sync, out_of_sync, error + replica_state = resource.Body("replica_state", type=str) + #: The UUID of the share to which the share instance belongs to. + share_id = resource.Body("share_id", type=str) + #: The share network ID where the resource is exported to. + share_network_id = resource.Body("share_network_id", type=str) + #: The UUID of the share server. + share_server_id = resource.Body("share_server_id", type=str) + #: The share or share instance status. + status = resource.Body("status", type=str) + + def _action(self, session, body, action='patch', microversion=None): + """Perform share instance actions given the message body""" + url = utils.urljoin(self.base_path, self.id, 'action') + headers = {'Accept': ''} + extra_attrs = {} + if microversion: + # Set microversion override + extra_attrs['microversion'] = microversion + else: + extra_attrs['microversion'] = \ + self._get_microversion(session, action=action) + response = session.post(url, json=body, headers=headers, **extra_attrs) + exceptions.raise_from_response(response) + return response + + def reset_status(self, session, reset_status): + """Reset share instance to given status""" + body = {"reset_status": {"status": reset_status}} + self._action(session, body) + + def force_delete(self, session): + """Force delete share instance""" + body = {"force_delete": None} + self._action(session, body, action='delete') diff --git a/openstack/tests/functional/shared_file_system/test_share_instance.py b/openstack/tests/functional/shared_file_system/test_share_instance.py new file mode 100644 index 000000000..86feea3c0 --- /dev/null +++ b/openstack/tests/functional/shared_file_system/test_share_instance.py @@ -0,0 +1,68 @@ +# 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 +from openstack.shared_file_system.v2 import share_instance as _share_instance +from openstack.tests.functional.shared_file_system import base + + +class ShareInstanceTest(base.BaseSharedFileSystemTest): + + min_microversion = '2.7' + + def setUp(self): + super(ShareInstanceTest, self).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 + instances_list = self.operator_cloud.share.share_instances() + self.SHARE_INSTANCE_ID = None + for i in instances_list: + if i.share_id == self.SHARE_ID: + self.SHARE_INSTANCE_ID = i.id + + def test_get(self): + sot = self.operator_cloud.share.get_share_instance( + self.SHARE_INSTANCE_ID) + assert isinstance(sot, _share_instance.ShareInstance) + self.assertEqual(self.SHARE_INSTANCE_ID, sot.id) + + def test_list_share_instances(self): + share_instances = self.operator_cloud.share.share_instances() + self.assertGreater(len(list(share_instances)), 0) + for share_instance in share_instances: + for attribute in ('id', 'name', 'created_at', + 'access_rules_status', + 'availability_zone'): + self.assertTrue(hasattr(share_instance, attribute)) + + def test_reset(self): + res = self.operator_cloud.share.reset_share_instance_status( + self.SHARE_INSTANCE_ID, 'error') + self.assertIsNone(res) + sot = self.operator_cloud.share.get_share_instance( + self.SHARE_INSTANCE_ID) + self.assertEqual('error', sot.status) + + def test_delete(self): + sot = self.operator_cloud.share.get_share_instance( + self.SHARE_INSTANCE_ID) + fdel = self.operator_cloud.share.delete_share_instance( + self.SHARE_INSTANCE_ID) + resource.wait_for_delete(self.operator_cloud.share, sot, + wait=self._wait_for_timeout, + interval=2) + self.assertIsNone(fdel) 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 82eeb3a5e..364fe5a2e 100644 --- a/openstack/tests/unit/shared_file_system/v2/test_proxy.py +++ b/openstack/tests/unit/shared_file_system/v2/test_proxy.py @@ -15,6 +15,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 share +from openstack.shared_file_system.v2 import share_instance from openstack.shared_file_system.v2 import share_network from openstack.shared_file_system.v2 import share_snapshot from openstack.shared_file_system.v2 import share_snapshot_instance @@ -61,6 +62,31 @@ class TestSharedFileSystemShare(TestSharedFileSystemProxy): def test_share_update(self): self.verify_update(self.proxy.update_share, share.Share) + def test_share_instances(self): + self.verify_list(self.proxy.share_instances, + share_instance.ShareInstance) + + def test_share_instance_get(self): + self.verify_get(self.proxy.get_share_instance, + share_instance.ShareInstance) + + def test_share_instance_reset(self): + self._verify( + "openstack.shared_file_system.v2.share_instance." + + "ShareInstance.reset_status", + self.proxy.reset_share_instance_status, + method_args=['id', 'available'], + expected_args=[self.proxy, 'available'], + ) + + def test_share_instance_delete(self): + self._verify( + "openstack.shared_file_system.v2.share_instance." + + "ShareInstance.force_delete", + self.proxy.delete_share_instance, + method_args=['id'], + expected_args=[self.proxy]) + @mock.patch("openstack.resource.wait_for_status") def test_wait_for(self, mock_wait): mock_resource = mock.Mock() diff --git a/openstack/tests/unit/shared_file_system/v2/test_share_instance.py b/openstack/tests/unit/shared_file_system/v2/test_share_instance.py new file mode 100644 index 000000000..338dc09fa --- /dev/null +++ b/openstack/tests/unit/shared_file_system/v2/test_share_instance.py @@ -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 unittest import mock + +from keystoneauth1 import adapter + +from openstack.shared_file_system.v2 import share_instance +from openstack.tests.unit import base + +IDENTIFIER = "75559a8b-c90c-42a7-bda2-edbe86acfb7b" + +EXAMPLE = { + "status": "available", + "progress": "100%", + "share_id": "d94a8548-2079-4be0-b21c-0a887acd31ca", + "availability_zone": "nova", + "replica_state": None, + "created_at": "2015-09-07T08:51:34.000000", + "cast_rules_to_readonly": False, + "share_network_id": "713df749-aac0-4a54-af52-10f6c991e80c", + "share_server_id": "ba11930a-bf1a-4aa7-bae4-a8dfbaa3cc73", + "host": "manila2@generic1#GENERIC1", + "access_rules_status": "active", + "id": IDENTIFIER +} + + +class TestShareInstances(base.TestCase): + + def test_basic(self): + share_instance_resource = share_instance.ShareInstance() + self.assertEqual('share_instances', + share_instance_resource.resources_key) + self.assertEqual('/share_instances', share_instance_resource.base_path) + self.assertTrue(share_instance_resource.allow_list) + self.assertFalse(share_instance_resource.allow_create) + self.assertTrue(share_instance_resource.allow_fetch) + self.assertFalse(share_instance_resource.allow_commit) + self.assertFalse(share_instance_resource.allow_delete) + + def test_make_share_instances(self): + share_instance_resource = share_instance.ShareInstance(**EXAMPLE) + self.assertEqual(EXAMPLE['status'], share_instance_resource.status) + self.assertEqual(EXAMPLE['progress'], share_instance_resource.progress) + self.assertEqual(EXAMPLE['share_id'], share_instance_resource.share_id) + self.assertEqual(EXAMPLE['availability_zone'], + share_instance_resource.availability_zone) + self.assertEqual(EXAMPLE['replica_state'], + share_instance_resource.replica_state) + self.assertEqual(EXAMPLE['created_at'], + share_instance_resource.created_at) + self.assertEqual(EXAMPLE['cast_rules_to_readonly'], + share_instance_resource.cast_rules_to_readonly) + self.assertEqual(EXAMPLE['share_network_id'], + share_instance_resource.share_network_id) + self.assertEqual(EXAMPLE['share_server_id'], + share_instance_resource.share_server_id) + self.assertEqual(EXAMPLE['host'], share_instance_resource.host) + self.assertEqual(EXAMPLE['access_rules_status'], + share_instance_resource.access_rules_status) + self.assertEqual(EXAMPLE['id'], share_instance_resource.id) + + +class TestShareInstanceActions(TestShareInstances): + + def setUp(self): + super(TestShareInstanceActions, self).setUp() + self.resp = mock.Mock() + self.resp.body = None + self.resp.status_code = 200 + self.resp.json = mock.Mock(return_value=self.resp.body) + self.sess = mock.Mock(spec=adapter.Adapter) + self.sess.default_microversion = '3.0' + self.sess.post = mock.Mock(return_value=self.resp) + self.sess._get_connection = mock.Mock(return_value=self.cloud) + + def test_reset_status(self): + sot = share_instance.ShareInstance(**EXAMPLE) + microversion = sot._get_microversion(self.sess, action='patch') + + self.assertIsNone(sot.reset_status(self.sess, 'active')) + + url = f'share_instances/{IDENTIFIER}/action' + body = {"reset_status": {"status": 'active'}} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, json=body, headers=headers, + microversion=microversion) + + def test_force_delete(self): + sot = share_instance.ShareInstance(**EXAMPLE) + microversion = sot._get_microversion(self.sess, action='delete') + + self.assertIsNone(sot.force_delete(self.sess)) + + url = f'share_instances/{IDENTIFIER}/action' + body = {'force_delete': None} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, json=body, headers=headers, + microversion=microversion) diff --git a/releasenotes/notes/add-shared-file-syste-share_instance-fffaea2d3a77ba24.yaml b/releasenotes/notes/add-shared-file-syste-share_instance-fffaea2d3a77ba24.yaml new file mode 100644 index 000000000..f14a7a4cf --- /dev/null +++ b/releasenotes/notes/add-shared-file-syste-share_instance-fffaea2d3a77ba24.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added support to list, get, reset status of, + and force delete share instances + (from shared file system service).