From 2f173cb150a4ee7a73158a3fa36ddab061534eb0 Mon Sep 17 00:00:00 2001 From: silvacarloss Date: Thu, 19 Aug 2021 13:17:49 -0300 Subject: [PATCH] [OSC] Implement share instance commands Added the implementation of the share instance commands to OSC. Commands are: - openstack share instance delete - openstack share instance list - openstack share instance set - openstack share instance show Co-Authored-By: Luisa Amaral Partially-Implements: bp openstack-client-support Change-Id: I8d29651d4e3a74b9ade67ea0a8f20808ca33bdc8 --- doc/source/cli/osc/v2/index.rst | 7 + manilaclient/osc/v2/share_instances.py | 223 ++++++++++ manilaclient/tests/unit/osc/v2/fakes.py | 59 +++ .../tests/unit/osc/v2/test_share_instances.py | 380 ++++++++++++++++++ setup.cfg | 4 + 5 files changed, 673 insertions(+) create mode 100644 manilaclient/osc/v2/share_instances.py create mode 100644 manilaclient/tests/unit/osc/v2/test_share_instances.py diff --git a/doc/source/cli/osc/v2/index.rst b/doc/source/cli/osc/v2/index.rst index 850a28363..e1edb2be9 100644 --- a/doc/source/cli/osc/v2/index.rst +++ b/doc/source/cli/osc/v2/index.rst @@ -47,6 +47,13 @@ shares .. autoprogram-cliff:: openstack.share.v2 :command: share revert +=============== +share instances +=============== + +.. autoprogram-cliff:: openstack.share.v2 + :command: share instance * + ================== share access rules ================== diff --git a/manilaclient/osc/v2/share_instances.py b/manilaclient/osc/v2/share_instances.py new file mode 100644 index 000000000..e9b5d45ef --- /dev/null +++ b/manilaclient/osc/v2/share_instances.py @@ -0,0 +1,223 @@ +# 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. + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils as osc_utils + +from manilaclient import api_versions +from manilaclient.common._i18n import _ +from manilaclient.common.apiclient import utils as apiutils +from manilaclient.common import cliutils + + +LOG = logging.getLogger(__name__) + + +class ShareInstanceDelete(command.Command): + """Forces the deletion of the share instance.""" + _description = _("Forces the deletion of a share instance") + + log = logging.getLogger(__name__ + ".ShareInstanceDelete") + + def get_parser(self, prog_name): + parser = super(ShareInstanceDelete, self).get_parser(prog_name) + parser.add_argument( + 'instance', + metavar="", + nargs="+", + help=_('ID of the share instance to delete.') + ) + parser.add_argument( + "--wait", + action='store_true', + default=False, + help=_("Wait for share instance deletion.") + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + number_of_deletion_failures = 0 + + for instance in parsed_args.instance: + try: + share_instance = apiutils.find_resource( + share_client.share_instances, instance) + + share_client.share_instances.force_delete(share_instance) + + if parsed_args.wait: + if not osc_utils.wait_for_delete( + manager=share_client.share_instances, + res_id=share_instance.id): + number_of_deletion_failures += 1 + + except Exception as e: + number_of_deletion_failures += 1 + LOG.error(_( + "Failed to delete a share instance with " + "ID '%(instance)s': %(e)s"), + {'instance': instance, 'e': e}) + if number_of_deletion_failures > 0: + msg = (_("%(number_of_deletion_failures)s of " + "%(total_of_instances)s instances failed " + "to delete.") % { + 'number_of_deletion_failures': number_of_deletion_failures, + 'total_of_instances': len(parsed_args.instance)}) + raise exceptions.CommandError(msg) + + +class ShareInstanceList(command.Lister): + """List share instances.""" + _description = _("List share instances") + + def get_parser(self, prog_name): + parser = super(ShareInstanceList, self).get_parser(prog_name) + parser.add_argument( + "--share", + metavar="", + default=None, + help=_("Name or ID of the share to list instances for.") + ) + parser.add_argument( + "--export-location", + metavar="", + default=None, + help=_("Export location to list instances for.") + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + instances = [] + kwargs = {} + + if parsed_args.share: + # Check if the share exists + share = osc_utils.find_resource( + share_client.shares, parsed_args.share) + + instances = share_client.shares.list_instances(share) + else: + if share_client.api_version < api_versions.APIVersion("2.35"): + if parsed_args.export_location: + raise exceptions.CommandError( + "Filtering by export location is only " + "available with manila API version >= 2.35") + else: + if parsed_args.export_location: + kwargs['export_location'] = parsed_args.export_location + instances = share_client.share_instances.list(**kwargs) + + columns = [ + 'ID', + 'Share ID', + 'Host', + 'Status', + 'Availability Zone', + 'Share Network ID', + 'Share Server ID', + 'Share Type ID', + ] + + data = (osc_utils.get_dict_properties( + instance._info, columns) for instance in instances) + + return (columns, data) + + +class ShareInstanceSet(command.Command): + """Set share instance""" + + _description = _("Explicitly reset share instance status") + + def get_parser(self, prog_name): + parser = super(ShareInstanceSet, self).get_parser(prog_name) + parser.add_argument( + "instance", + metavar="", + help=_("Instance to be modified.") + ) + parser.add_argument( + "--status", + metavar="", + help=_('Indicate which state to assign the instance. Options are: ' + 'available, error, creating, deleting,' + 'error_deleting, migrating, migrating_to, server_migrating.' + ) + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + instance = osc_utils.find_resource( + share_client.share_instances, + parsed_args.instance) + + if parsed_args.status: + try: + share_client.share_instances.reset_state( + instance, + parsed_args.status + ) + except Exception as e: + LOG.error(_( + "Failed to set status '%(status)s': %(exception)s"), + {'status': parsed_args.status, 'exception': e}) + raise exceptions.CommandError(_("Set operation failed")) + + if not instance or not parsed_args.status: + raise exceptions.CommandError(_( + "Nothing to set. Please define a '--status'.")) + + +class ShareInstanceShow(command.ShowOne): + """Show share instance.""" + _description = _("Show share instance") + + def get_parser(self, prog_name): + parser = super(ShareInstanceShow, self).get_parser(prog_name) + parser.add_argument( + "instance", + metavar="", + help=_("ID of the share instance.") + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + instance = osc_utils.find_resource( + share_client.share_instances, + parsed_args.instance) + + export_locations = share_client.share_instance_export_locations.list( + instance) + + instance._info['export_locations'] = [] + for export_location in export_locations: + export_location._info.pop('links', None) + instance._info['export_locations'].append(export_location._info) + + if parsed_args.formatter == 'table': + instance._info['export_locations'] = ( + cliutils.transform_export_locations_to_string_view( + instance._info['export_locations'])) + + instance._info.pop('links', None) + + return self.dict2columns(instance._info) diff --git a/manilaclient/tests/unit/osc/v2/fakes.py b/manilaclient/tests/unit/osc/v2/fakes.py index a24847d57..9550a3514 100644 --- a/manilaclient/tests/unit/osc/v2/fakes.py +++ b/manilaclient/tests/unit/osc/v2/fakes.py @@ -40,6 +40,7 @@ class FakeShareClient(object): self.share_replicas = mock.Mock() self.share_replica_export_locations = mock.Mock() self.shares.resource_class = osc_fakes.FakeResource(None, {}) + self.share_instance_export_locations = mock.Mock() self.share_export_locations = mock.Mock() self.share_snapshot_instance_export_locations = mock.Mock() self.share_export_locations.resource_class = ( @@ -47,6 +48,7 @@ class FakeShareClient(object): self.messages = mock.Mock() self.availability_zones = mock.Mock() self.services = mock.Mock() + self.share_instances = mock.Mock() self.pools = mock.Mock() @@ -904,3 +906,60 @@ class FakeSharePools(object): share_pools.append( FakeSharePools.create_one_share_pool(attrs)) return share_pools + + +class FakeShareInstance(object): + """Fake a share instance""" + + @staticmethod + def create_one_share_instance(attrs=None, methods=None): + """Create a fake share instance + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with project_id, resource and so on + """ + + attrs = attrs or {} + methods = methods or {} + + share_instance = { + 'status': None, + 'progress': None, + 'share_id': 'share-id-' + uuid.uuid4().hex, + 'availability_zone': None, + 'replica_state': None, + 'created_at': datetime.datetime.now().isoformat(), + 'cast_rules_to_readonly': False, + 'share_network_id': 'sn-id-' + uuid.uuid4().hex, + 'share_server_id': 'ss-id-' + uuid.uuid4().hex, + 'host': None, + 'access_rules_status': None, + 'id': 'instance-id-' + uuid.uuid4().hex + } + + share_instance.update(attrs) + share_instance = osc_fakes.FakeResource(info=copy.deepcopy( + share_instance), + methods=methods, + loaded=True) + return share_instance + + @staticmethod + def create_share_instances(attrs=None, count=2): + """Create multiple fake instances. + + :param Dictionary attrs: + A dictionary with all attributes + :param Integer count: + The number of share instances to be faked + :return: + A list of FakeResource objects + """ + + share_instances = [] + for n in range(count): + share_instances.append( + FakeShareInstance.create_one_share_instance(attrs)) + return share_instances diff --git a/manilaclient/tests/unit/osc/v2/test_share_instances.py b/manilaclient/tests/unit/osc/v2/test_share_instances.py new file mode 100644 index 000000000..7470d5daa --- /dev/null +++ b/manilaclient/tests/unit/osc/v2/test_share_instances.py @@ -0,0 +1,380 @@ +# 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 osc_lib import exceptions +from osc_lib import utils as oscutils + +from manilaclient.common import cliutils +from manilaclient.osc import utils +from manilaclient.osc.v2 import share_instances as osc_share_instances + +from manilaclient import api_versions +from manilaclient.tests.unit.osc import osc_utils +from manilaclient.tests.unit.osc.v2 import fakes as manila_fakes + + +class TestShareInstance(manila_fakes.TestShare): + + def setUp(self): + super(TestShareInstance, self).setUp() + + self.shares_mock = self.app.client_manager.share.shares + self.shares_mock.reset_mock() + + self.instances_mock = self.app.client_manager.share.share_instances + self.instances_mock.reset_mock() + + self.share_instance_export_locations_mock = ( + self.app.client_manager.share.share_instance_export_locations) + self.share_instance_export_locations_mock.reset_mock() + + self.app.client_manager.share.api_version = api_versions.APIVersion( + api_versions.MAX_VERSION) + + +class TestShareInstanceList(TestShareInstance): + columns = [ + 'id', + 'share_id', + 'host', + 'status', + 'availability_zone', + 'share_network_id', + 'share_server_id', + 'share_type_id', + ] + column_headers = utils.format_column_headers(columns) + + def setUp(self): + super(TestShareInstanceList, self).setUp() + + self.instances_list = ( + manila_fakes.FakeShareInstance.create_share_instances(count=2)) + self.instances_mock.list.return_value = self.instances_list + + self.share = manila_fakes.FakeShare.create_one_share() + self.shares_mock.get.return_value = self.share + + self.shares_mock.list_instances.return_value = self.instances_list + self.shares_mock.list_instances.return_value = self.instances_list + + self.instance_values = (oscutils.get_dict_properties( + instance._info, self.columns) for instance in self.instances_list) + + self.cmd = osc_share_instances.ShareInstanceList(self.app, None) + + def test_share_instance_list(self): + argslist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, argslist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertIs(True, self.instances_mock.list.called) + + self.assertEqual(self.column_headers, columns) + self.assertEqual(list(self.instance_values), list(data)) + + def test_share_instance_list_by_share(self): + argslist = [ + '--share', self.share['id'] + ] + verifylist = [ + ('share', self.share.id) + ] + + parsed_args = self.check_parser(self.cmd, argslist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.shares_mock.list_instances.assert_called_with(self.share) + + self.assertEqual(self.column_headers, columns) + self.assertEqual(list(self.instance_values), list(data)) + + def test_share_instance_list_by_export_location(self): + fake_export_location = '10.1.1.0:/fake_share_el' + argslist = [ + '--export-location', fake_export_location + ] + verifylist = [ + ('export_location', fake_export_location) + ] + + parsed_args = self.check_parser(self.cmd, argslist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.instances_mock.list.assert_called_with( + export_location=fake_export_location) + + self.assertEqual(self.column_headers, columns) + self.assertEqual(list(self.instance_values), list(data)) + + def test_share_instance_list_by_export_location_invalid_version(self): + fake_export_location = '10.1.1.0:/fake_share_el' + argslist = [ + '--export-location', fake_export_location + ] + verifylist = [ + ('export_location', fake_export_location) + ] + self.app.client_manager.share.api_version = api_versions.APIVersion( + '2.34') + + parsed_args = self.check_parser(self.cmd, argslist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + +class TestShareInstanceDelete(TestShareInstance): + + def setUp(self): + super(TestShareInstanceDelete, self).setUp() + self.share_instance = ( + manila_fakes.FakeShareInstance.create_one_share_instance()) + self.instances_mock.get.return_value = self.share_instance + + self.cmd = osc_share_instances.ShareInstanceDelete(self.app, None) + + def test_share_instance_delete_missing_args(self): + arglist = [] + verifylist = [] + + self.assertRaises(osc_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_share_instance_delete(self): + arglist = [ + self.share_instance.id + ] + verifylist = [ + ('instance', [self.share_instance.id]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.instances_mock.force_delete.assert_called_with( + self.share_instance) + self.assertIsNone(result) + + def test_share_instance_delete_multiple(self): + share_instances = ( + manila_fakes.FakeShareInstance.create_share_instances(count=2)) + instance_ids = [instance.id for instance in share_instances] + arglist = instance_ids + verifylist = [('instance', instance_ids)] + self.instances_mock.get.side_effect = share_instances + + delete_calls = [ + mock.call(instance) for instance in share_instances] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.instances_mock.force_delete.assert_has_calls(delete_calls) + self.assertEqual(self.instances_mock.force_delete.call_count, + len(share_instances)) + self.assertIsNone(result) + + def test_share_instance_delete_exception(self): + arglist = [ + self.share_instance.id + ] + verifylist = [ + ('instance', [self.share_instance.id]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.instances_mock.force_delete.side_effect = ( + exceptions.CommandError()) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_share_instance_delete_wait(self): + arglist = [ + self.share_instance.id, + '--wait' + ] + verifylist = [ + ('instance', [self.share_instance.id]), + ('wait', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('osc_lib.utils.wait_for_delete', return_value=True): + result = self.cmd.take_action(parsed_args) + + self.instances_mock.force_delete.assert_called_with( + self.share_instance) + self.instances_mock.get.assert_called_with(self.share_instance.id) + self.assertIsNone(result) + + def test_share_instance_delete_wait_exception(self): + arglist = [ + self.share_instance.id, + '--wait' + ] + verifylist = [ + ('instance', [self.share_instance.id]), + ('wait', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('osc_lib.utils.wait_for_delete', return_value=False): + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args + ) + + +class TestShareInstanceShow(TestShareInstance): + + def setUp(self): + super(TestShareInstanceShow, self).setUp() + + self.share_instance = ( + manila_fakes.FakeShareInstance.create_one_share_instance() + ) + self.instances_mock.get.return_value = self.share_instance + + self.export_locations = ( + [manila_fakes.FakeShareExportLocation.create_one_export_location() + for i in range(2)]) + + self.share_instance_export_locations_mock.list.return_value = ( + self.export_locations) + + self.cmd = osc_share_instances.ShareInstanceShow(self.app, None) + + self.data = tuple(self.share_instance._info.values()) + self.columns = tuple(self.share_instance._info.keys()) + + def test_share_instance_show_missing_args(self): + arglist = [] + verifylist = [] + + self.assertRaises( + osc_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_share_instance_show(self): + expected_columns = tuple(self.share_instance._info.keys()) + + expected_data_dic = tuple() + + for column in expected_columns: + expected_data_dic += (self.share_instance._info[column],) + + expected_columns += ('export_locations',) + expected_data_dic += (dict(self.export_locations[0]),) + + cliutils.transform_export_locations_to_string_view = mock.Mock() + cliutils.transform_export_locations_to_string_view.return_value = dict( + self.export_locations[0]) + + arglist = [ + self.share_instance.id + ] + verifylist = [ + ('instance', self.share_instance.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.instances_mock.get.assert_called_with( + self.share_instance.id + ) + + self.assertCountEqual(expected_columns, columns) + self.assertCountEqual(expected_data_dic, data) + + +class TestShareInstanceSet(TestShareInstance): + + def setUp(self): + super(TestShareInstanceSet, self).setUp() + + self.share_instance = ( + manila_fakes.FakeShareInstance.create_one_share_instance()) + + self.instances_mock.get.return_value = self.share_instance + + self.cmd = osc_share_instances.ShareInstanceSet(self.app, None) + + def test_share_instance_set_status(self): + new_status = 'available' + arglist = [ + self.share_instance.id, + '--status', new_status + ] + verifylist = [ + ('instance', self.share_instance.id), + ('status', new_status) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.instances_mock.reset_state.assert_called_with( + self.share_instance, + new_status) + self.assertIsNone(result) + + def test_share_instance_set_status_exception(self): + new_status = 'available' + arglist = [ + self.share_instance.id, + '--status', new_status + ] + verifylist = [ + ('instance', self.share_instance.id), + ('status', new_status) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.instances_mock.reset_state.side_effect = Exception() + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_share_instance_set_nothing_defined(self): + arglist = [ + self.share_instance.id, + ] + verifylist = [ + ('instance', self.share_instance.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) diff --git a/setup.cfg b/setup.cfg index c92c2d82f..96711bad3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -100,6 +100,10 @@ openstack.share.v2 = share_service_set = manilaclient.osc.v2.services:SetShareService share_service_list = manilaclient.osc.v2.services:ListShareService share_pool_list = manilaclient.osc.v2.share_pools:ListSharePools + share_instance_delete = manilaclient.osc.v2.share_instances:ShareInstanceDelete + share_instance_list = manilaclient.osc.v2.share_instances:ShareInstanceList + share_instance_set = manilaclient.osc.v2.share_instances:ShareInstanceSet + share_instance_show = manilaclient.osc.v2.share_instances:ShareInstanceShow [coverage:run] omit = manilaclient/tests/*