diff --git a/manilaclient/api_versions.py b/manilaclient/api_versions.py index f54f95ba5..a36e95ebf 100644 --- a/manilaclient/api_versions.py +++ b/manilaclient/api_versions.py @@ -27,7 +27,7 @@ from manilaclient import utils LOG = logging.getLogger(__name__) -MAX_VERSION = '2.46' +MAX_VERSION = '2.47' MIN_VERSION = '2.0' DEPRECATED_VERSION = '1.0' _VERSIONED_METHOD_MAP = {} diff --git a/manilaclient/config.py b/manilaclient/config.py index 527e719a5..2e927849c 100644 --- a/manilaclient/config.py +++ b/manilaclient/config.py @@ -145,6 +145,17 @@ share_opts = [ default="stack", help="Username, that will be used in share access tests for " "user type of access."), + cfg.StrOpt("replication_type", + default="readable", + choices=["readable", "writable", "dr"], + help="Replication type to be used when running replication " + "tests. This option is ignored if run_replication_tests " + "is set to False."), + cfg.BoolOpt("run_replication_tests", + default=True, + help="Defines whether to run tests for share replication " + "or not. Disable this feature if manila driver used " + "doesn't support share replication."), cfg.BoolOpt("run_snapshot_tests", default=True, help="Defines whether to run tests that use share snapshots " diff --git a/manilaclient/tests/functional/base.py b/manilaclient/tests/functional/base.py index ca3faa5d9..65a63842b 100644 --- a/manilaclient/tests/functional/base.py +++ b/manilaclient/tests/functional/base.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import re import traceback from oslo_log import log @@ -111,6 +112,11 @@ class BaseTestCase(base.ClientTestBase): res_id, microversion=res["microversion"]) client.wait_for_snapshot_deletion( res_id, microversion=res["microversion"]) + elif res["type"] is "share_replica": + client.delete_share_replica( + res_id, microversion=res["microversion"]) + client.wait_for_share_replica_deletion( + res_id, microversion=res["microversion"]) else: LOG.warning("Provided unsupported resource type for " "cleanup '%s'. Skipping.", res["type"]) @@ -238,8 +244,7 @@ class BaseTestCase(base.ClientTestBase): public=False, snapshot=None, metadata=None, client=None, cleanup_in_class=False, wait_for_creation=True, microversion=None): - if client is None: - client = cls.get_admin_client() + client = client or cls.get_admin_client() data = { 'share_protocol': share_protocol or client.share_protocol, 'size': size or 1, @@ -250,12 +255,13 @@ class BaseTestCase(base.ClientTestBase): 'metadata': metadata, 'microversion': microversion, } - share_network = share_network or client.share_network + share_type = share_type or CONF.share_type - if share_network: - data['share_network'] = share_network - if share_type: - data['share_type'] = share_type + share_network = share_network or cls._determine_share_network_to_use( + client, share_type, microversion=microversion) + + data['share_type'] = share_type + data['share_network'] = share_network share = client.create_share(**data) resource = { "type": "share", @@ -271,6 +277,18 @@ class BaseTestCase(base.ClientTestBase): client.wait_for_share_status(share['id'], 'available') return share + @classmethod + def _determine_share_network_to_use(cls, client, share_type, + microversion=None): + """Determine what share network we need from the share type.""" + + # Get share type, determine if we need the share network + share_type = client.get_share_type(share_type, + microversion=microversion) + dhss_pattern = re.compile('driver_handles_share_servers : ([a-zA-Z]+)') + dhss = dhss_pattern.search(share_type['required_extra_specs']).group(1) + return client.share_network if dhss.lower() == 'true' else None + @classmethod def create_security_service(cls, type='ldap', name=None, description=None, dns_ip=None, server=None, domain=None, @@ -366,3 +384,27 @@ class BaseTestCase(base.ClientTestBase): else: cls.method_resources.insert(0, resource) return message + + @classmethod + def create_share_replica(cls, share_id, client=None, + wait_for_creation=True, cleanup_in_class=False, + microversion=None): + client = client or cls.get_user_client() + + share_replica = client.create_share_replica( + share_id, microversion=microversion) + if wait_for_creation: + share_replica = client.wait_for_share_replica_status( + share_replica['id']) + + resource = { + "type": "share_replica", + "id": share_replica["id"], + "client": client, + "microversion": microversion, + } + if cleanup_in_class: + cls.class_resources.insert(0, resource) + else: + cls.method_resources.insert(0, resource) + return share_replica diff --git a/manilaclient/tests/functional/client.py b/manilaclient/tests/functional/client.py index 5e7875cb1..ab225d7e1 100644 --- a/manilaclient/tests/functional/client.py +++ b/manilaclient/tests/functional/client.py @@ -35,6 +35,7 @@ SHARE_TYPE = 'share_type' SHARE_NETWORK = 'share_network' SHARE_SERVER = 'share_server' SNAPSHOT = 'snapshot' +SHARE_REPLICA = 'share_replica' def not_found_wrapper(f): @@ -136,6 +137,8 @@ class ManilaCLIClient(base.CLIClient): func = self.is_snapshot_deleted elif res_type == MESSAGE: func = self.is_message_deleted + elif res_type == SHARE_REPLICA: + func = self.is_share_replica_deleted else: raise exceptions.InvalidResource(message=res_type) @@ -274,12 +277,15 @@ class ManilaCLIClient(base.CLIClient): def get_share_type(self, share_type, microversion=None): """Get share type. - :param share_type: str -- Name or ID of share type + :param share_type: str -- Name or ID of share type, or None to + retrieve default share type """ share_types = self.list_share_types(True, microversion=microversion) - for st in share_types: - if share_type in (st['ID'], st['Name']): - return st + for stype in share_types: + if share_type is None and stype["is_default"] == 'YES': + return stype + elif share_type in (stype['ID'], stype['Name']): + return stype raise tempest_lib_exc.NotFound() def is_share_type_deleted(self, share_type, microversion=None): @@ -1496,3 +1502,118 @@ class ManilaCLIClient(base.CLIClient): self.wait_for_resource_deletion( MESSAGE, res_id=message, interval=3, timeout=60, microversion=microversion) + + # Share replicas + + def create_share_replica(self, share, microversion=None): + """Create a share replica. + + :param share: str -- Name or ID of a share to create a replica of + """ + cmd = "share-replica-create %s" % share + replica = self.manila(cmd, microversion=microversion) + return output_parser.details(replica) + + @not_found_wrapper + def get_share_replica(self, replica, microversion=None): + cmd = "share-replica-show %s" % replica + replica = self.manila(cmd, microversion=microversion) + return output_parser.details(replica) + + @not_found_wrapper + @forbidden_wrapper + def delete_share_replica(self, share_replica, microversion=None): + """Deletes share replica by ID.""" + return self.manila( + "share-replica-delete %s" % share_replica, + microversion=microversion) + + def is_share_replica_deleted(self, replica, microversion=None): + """Indicates whether a share replica is deleted or not. + + :param replica: str -- ID of share replica + """ + try: + self.get_share_replica(replica, microversion=microversion) + return False + except tempest_lib_exc.NotFound: + return True + + def wait_for_share_replica_deletion(self, replica, microversion=None): + """Wait for share replica deletion by its ID. + + :param replica: text -- ID of share replica + """ + self.wait_for_resource_deletion( + SHARE_REPLICA, res_id=replica, interval=3, timeout=60, + microversion=microversion) + + def wait_for_share_replica_status(self, share_replica, + status="available", + microversion=None): + """Waits for a share replica to reach a given status.""" + replica = self.get_share_replica(share_replica, + microversion=microversion) + share_replica_status = replica['status'] + start = int(time.time()) + + while share_replica_status != status: + time.sleep(self.build_interval) + replica = self.get_share_replica(share_replica, + microversion=microversion) + share_replica_status = replica['status'] + + if share_replica_status == status: + return replica + elif 'error' in share_replica_status.lower(): + raise exceptions.ShareReplicaBuildErrorException( + replica=share_replica) + + if int(time.time()) - start >= self.build_timeout: + message = ( + "Share replica %(id)s failed to reach %(status)s " + "status within the required time " + "(%(build_timeout)s s)." % { + "id": share_replica, "status": status, + "build_timeout": self.build_timeout}) + raise tempest_lib_exc.TimeoutException(message) + return replica + + @not_found_wrapper + @forbidden_wrapper + def list_share_replica_export_locations(self, share_replica, + columns=None, microversion=None): + """List share replica export locations. + + :param share_replica: str -- ID of share replica. + :param columns: str -- comma separated string of columns. + Example, "--columns id,path". + :param microversion: API microversion to be used for request. + """ + cmd = "share-replica-export-location-list %s" % share_replica + if columns is not None: + cmd += " --columns " + columns + export_locations_raw = self.manila(cmd, microversion=microversion) + export_locations = utils.listing(export_locations_raw) + return export_locations + + @not_found_wrapper + @forbidden_wrapper + def get_share_replica_export_location(self, share_replica, + export_location_uuid, + microversion=None): + """Returns an export location by share replica and export location ID. + + :param share_replica: str -- ID of share replica. + :param export_location_uuid: str -- UUID of an export location. + :param microversion: API microversion to be used for request. + """ + export_raw = self.manila( + 'share-replica-export-location-show ' + '%(share_replica)s %(el_uuid)s' % { + 'share_replica': share_replica, + 'el_uuid': export_location_uuid, + }, + microversion=microversion) + export = output_parser.details(export_raw) + return export diff --git a/manilaclient/tests/functional/exceptions.py b/manilaclient/tests/functional/exceptions.py index ac8bf2070..0ec49d6ef 100644 --- a/manilaclient/tests/functional/exceptions.py +++ b/manilaclient/tests/functional/exceptions.py @@ -44,6 +44,11 @@ class ShareBuildErrorException(exceptions.TempestException): message = "Share %(share)s failed to build and is in ERROR status." +class ShareReplicaBuildErrorException(exceptions.TempestException): + message = ("Share replica %(replica)s failed to build and is in ERROR " + "status.") + + class SnapshotBuildErrorException(exceptions.TempestException): message = "Snapshot %(snapshot)s failed to build and is in ERROR status." diff --git a/manilaclient/tests/functional/test_share_replica_export_locations.py b/manilaclient/tests/functional/test_share_replica_export_locations.py new file mode 100644 index 000000000..be780165b --- /dev/null +++ b/manilaclient/tests/functional/test_share_replica_export_locations.py @@ -0,0 +1,107 @@ +# All Rights Reserved. +# +# 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 ddt +from oslo_utils import uuidutils +import testtools + +from manilaclient import config +from manilaclient.tests.functional import base +from manilaclient.tests.functional import utils + +CONF = config.CONF + + +@ddt.ddt +@testtools.skipUnless(CONF.run_replication_tests, + "Replication tests are disabled.") +@utils.skip_if_microversion_not_supported('2.47') +class ShareReplicaExportLocationsTest(base.BaseTestCase): + + @classmethod + def setUpClass(cls): + super(ShareReplicaExportLocationsTest, cls).setUpClass() + + def _create_share_and_replica(self): + replication_type = CONF.replication_type + share_type = self.create_share_type( + driver_handles_share_servers=False, + extra_specs={'replication_type': replication_type}, + cleanup_in_class=False) + share = self.create_share(share_type=share_type['ID'], + client=self.get_user_client()) + share_replica = self.create_share_replica(share['id']) + return share, share_replica + + @ddt.data('admin', 'user') + def test_list_share_export_locations(self, role): + share, share_replica = self._create_share_and_replica() + client = self.admin_client if role == 'admin' else self.user_client + export_locations = client.list_share_replica_export_locations( + share_replica['id']) + + self.assertGreater(len(export_locations), 0) + expected_keys = ['ID', 'Path', 'Preferred', 'Replica State', + 'Availability Zone'] + + for el in export_locations: + for key in expected_keys: + self.assertIn(key, el) + self.assertTrue(uuidutils.is_uuid_like(el['ID'])) + self.assertIn(el['Preferred'], ('True', 'False')) + + @ddt.data('admin', 'user') + def test_list_share_export_locations_with_columns(self, role): + share, share_replica = self._create_share_and_replica() + client = self.admin_client if role == 'admin' else self.user_client + export_locations = client.list_share_replica_export_locations( + share_replica['id'], columns='id,path') + + self.assertGreater(len(export_locations), 0) + expected_keys = ('Id', 'Path') + unexpected_keys = ('Updated At', 'Created At') + for el in export_locations: + for key in expected_keys: + self.assertIn(key, el) + for key in unexpected_keys: + self.assertNotIn(key, el) + self.assertTrue(uuidutils.is_uuid_like(el['Id'])) + + @ddt.data('admin', 'user') + def test_get_share_replica_export_location(self, role): + share, share_replica = self._create_share_and_replica() + client = self.admin_client if role == 'admin' else self.user_client + export_locations = client.list_share_replica_export_locations( + share_replica['id']) + + el = client.get_share_replica_export_location( + share_replica['id'], export_locations[0]['ID']) + + expected_keys = ['path', 'updated_at', 'created_at', 'id', + 'preferred', 'replica_state', 'availability_zone'] + if role == 'admin': + expected_keys.extend(['is_admin_only', 'share_instance_id']) + for key in expected_keys: + self.assertIn(key, el) + if role == 'admin': + self.assertTrue(uuidutils.is_uuid_like(el['share_instance_id'])) + self.assertIn(el['is_admin_only'], ('True', 'False')) + self.assertTrue(uuidutils.is_uuid_like(el['id'])) + self.assertIn(el['preferred'], ('True', 'False')) + for list_k, get_k in ( + ('ID', 'id'), ('Path', 'path'), ('Preferred', 'preferred'), + ('Replica State', 'replica_state'), + ('Availability Zone', 'availability_zone')): + self.assertEqual( + export_locations[0][list_k], el[get_k]) diff --git a/manilaclient/tests/unit/v2/fakes.py b/manilaclient/tests/unit/v2/fakes.py index 6196fc321..165e370c0 100644 --- a/manilaclient/tests/unit/v2/fakes.py +++ b/manilaclient/tests/unit/v2/fakes.py @@ -830,6 +830,26 @@ class FakeHTTPClient(fakes.FakeHTTPClient): replicas = {'share_replica': self.fake_share_replica} return (200, {}, replicas) + def get_share_replicas_5678_export_locations(self, **kw): + export_locations = { + 'export_locations': [ + get_fake_export_location(), + ] + } + return (200, {}, export_locations) + + def get_share_replicas_1234_export_locations(self, **kw): + export_locations = { + 'export_locations': [ + get_fake_export_location(), + ] + } + return (200, {}, export_locations) + + def get_share_replicas_1234_export_locations_fake_el_uuid(self, **kw): + export_location = {'export_location': get_fake_export_location()} + return (200, {}, export_location) + def post_share_replicas(self, **kw): return (202, {}, {'share_replica': self.fake_share_replica}) diff --git a/manilaclient/tests/unit/v2/test_share_replica_export_locations.py b/manilaclient/tests/unit/v2/test_share_replica_export_locations.py new file mode 100644 index 000000000..c32bc173d --- /dev/null +++ b/manilaclient/tests/unit/v2/test_share_replica_export_locations.py @@ -0,0 +1,50 @@ +# All Rights Reserved. +# +# 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 ddt +import mock + +from manilaclient import api_versions +from manilaclient.tests.unit import utils +from manilaclient.tests.unit.v2 import fakes +from manilaclient.v2 import share_replica_export_locations + +cs = fakes.FakeClient() + + +@ddt.ddt +class ShareReplicaExportLocationsTest(utils.TestCase): + + def _get_manager(self, microversion): + version = api_versions.APIVersion(microversion) + mock_microversion = mock.Mock(api_version=version) + return ( + share_replica_export_locations.ShareReplicaExportLocationManager( + api=mock_microversion) + ) + + def test_list_share_replica_export_locations(self): + share_replica_id = '1234' + cs.share_replica_export_locations.list(share_replica_id) + cs.assert_called( + 'GET', '/share-replicas/%s/export-locations' % share_replica_id) + + def test_get_share_replica_export_location(self): + share_replica_id = '1234' + el_uuid = 'fake_el_uuid' + cs.share_replica_export_locations.get(share_replica_id, el_uuid) + url = ('/share-replicas/%(share_replica_id)s/export-locations/' + '%(el_uuid)s') + payload = {'share_replica_id': share_replica_id, 'el_uuid': el_uuid} + cs.assert_called('GET', url % payload) diff --git a/manilaclient/tests/unit/v2/test_shell.py b/manilaclient/tests/unit/v2/test_shell.py index c07f3a735..e75068b13 100644 --- a/manilaclient/tests/unit/v2/test_shell.py +++ b/manilaclient/tests/unit/v2/test_shell.py @@ -2751,7 +2751,7 @@ class ShellTest(test_utils.TestCase): self.run_command('share-replica-show 5678') - self.assert_called('GET', '/share-replicas/5678') + self.assert_called_anytime('GET', '/share-replicas/5678') @ddt.data('promote', 'resync') @mock.patch.object(shell_v2, '_find_share_replica', mock.Mock()) @@ -2766,6 +2766,38 @@ class ShellTest(test_utils.TestCase): 'POST', '/share-replicas/1234/action', body={action.replace('-', '_'): None}) + @mock.patch.object(shell_v2, '_find_share_replica', mock.Mock()) + @mock.patch.object(cliutils, 'print_list', mock.Mock()) + @ddt.data(None, "replica_state,path") + def test_share_replica_export_location_list(self, columns): + fake_replica = type('FakeShareReplica', (object,), {'id': '1234'}) + shell_v2._find_share_replica.return_value = fake_replica + cmd = 'share-replica-export-location-list ' + fake_replica.id + if columns is not None: + cmd = cmd + ' --columns=%s' % columns + expected_columns = list(map(lambda x: x.strip().title(), + columns.split(","))) + else: + expected_columns = [ + 'ID', 'Availability Zone', 'Replica State', + 'Preferred', 'Path' + ] + + self.run_command(cmd) + + self.assert_called( + 'GET', '/share-replicas/1234/export-locations') + cliutils.print_list.assert_called_with(mock.ANY, expected_columns) + + @mock.patch.object(shell_v2, '_find_share_replica', mock.Mock()) + def test_share_replica_export_location_show(self): + fake_replica = type('FakeShareReplica', (object,), {'id': '1234'}) + shell_v2._find_share_replica.return_value = fake_replica + self.run_command( + 'share-replica-export-location-show 1234 fake-el-uuid') + self.assert_called( + 'GET', '/share-replicas/1234/export-locations/fake-el-uuid') + @ddt.data('reset-state', 'reset-replica-state') @mock.patch.object(shell_v2, '_find_share_replica', mock.Mock()) def test_share_replica_reset_state_cmds(self, action): diff --git a/manilaclient/v2/client.py b/manilaclient/v2/client.py index c1482781f..fa80a732f 100644 --- a/manilaclient/v2/client.py +++ b/manilaclient/v2/client.py @@ -37,6 +37,7 @@ from manilaclient.v2 import share_groups from manilaclient.v2 import share_instance_export_locations from manilaclient.v2 import share_instances from manilaclient.v2 import share_networks +from manilaclient.v2 import share_replica_export_locations from manilaclient.v2 import share_replicas from manilaclient.v2 import share_servers from manilaclient.v2 import share_snapshot_export_locations @@ -237,6 +238,9 @@ class Client(object): self.share_type_access = share_type_access.ShareTypeAccessManager(self) self.share_servers = share_servers.ShareServerManager(self) self.share_replicas = share_replicas.ShareReplicaManager(self) + self.share_replica_export_locations = ( + share_replica_export_locations.ShareReplicaExportLocationManager( + self)) self.pools = scheduler_stats.PoolManager(self) self.share_access_rules = ( share_access_rules.ShareAccessRuleManager(self)) diff --git a/manilaclient/v2/share_replica_export_locations.py b/manilaclient/v2/share_replica_export_locations.py new file mode 100644 index 000000000..b4a77e459 --- /dev/null +++ b/manilaclient/v2/share_replica_export_locations.py @@ -0,0 +1,55 @@ +# All Rights Reserved. +# +# 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 manilaclient import api_versions +from manilaclient import base +from manilaclient.common.apiclient import base as common_base + + +class ShareReplicaExportLocation(common_base.Resource): + """Resource class for a share replica export location.""" + + def __repr__(self): + return "" % self.id + + def __getitem__(self, key): + return self._info[key] + + +class ShareReplicaExportLocationManager(base.ManagerWithFind): + """Manage :class:`ShareInstanceExportLocation` resources.""" + resource_class = ShareReplicaExportLocation + + @api_versions.wraps("2.47") + @api_versions.experimental_api + def list(self, share_replica, search_opts=None): + """List all share replica export locations.""" + share_replica_id = common_base.getid(share_replica) + return self._list( + "/share-replicas/%s/export-locations" % share_replica_id, + "export_locations") + + @api_versions.wraps("2.47") + @api_versions.experimental_api + def get(self, share_replica, export_location): + """Get a share replica export location.""" + share_replica_id = common_base.getid(share_replica) + export_location_id = common_base.getid(export_location) + return self._get( + ("/share-replicas/%(share_replica_id)s/export-locations/" + "%(export_location_id)s") % { + "share_replica_id": share_replica_id, + "export_location_id": export_location_id, + }, + "export_location") diff --git a/manilaclient/v2/shell.py b/manilaclient/v2/shell.py index 660207492..5eec22cd9 100644 --- a/manilaclient/v2/shell.py +++ b/manilaclient/v2/shell.py @@ -68,14 +68,19 @@ def _find_share(cs, share): def _transform_export_locations_to_string_view(export_locations): export_locations_string_view = '' + replica_export_location_ignored_keys = ( + 'replica_state', 'availability_zone', 'share_replica_id') for el in export_locations: if hasattr(el, '_info'): export_locations_dict = el._info else: export_locations_dict = el for k, v in export_locations_dict.items(): - export_locations_string_view += '\n%(k)s = %(v)s' % { - 'k': k, 'v': v} + # NOTE(gouthamr): We don't want to show replica related info + # twice in the output, so ignore those. + if k not in replica_export_location_ignored_keys: + export_locations_string_view += '\n%(k)s = %(v)s' % { + 'k': k, 'v': v} return export_locations_string_view @@ -195,12 +200,24 @@ def _find_share_replica(cs, replica): return apiclient_utils.find_resource(cs.share_replicas, replica) +@api_versions.wraps("2.11", "2.46") def _print_share_replica(cs, replica): info = replica._info.copy() info.pop('links', None) cliutils.print_dict(info) +@api_versions.wraps("2.47") # noqa +def _print_share_replica(cs, replica): + info = replica._info.copy() + info.pop('links', None) + if info.get('export_locations'): + info['export_locations'] = ( + _transform_export_locations_to_string_view( + info['export_locations'])) + cliutils.print_dict(info) + + @api_versions.experimental_api @api_versions.wraps("2.31") def _find_share_group(cs, share_group): @@ -5006,7 +5023,7 @@ def do_share_replica_create(cs, args): 'replica', metavar='', help='ID of the share replica.') -@api_versions.wraps("2.11") +@api_versions.wraps("2.11", "2.46") def do_share_replica_show(cs, args): """Show details about a replica (Experimental).""" @@ -5014,6 +5031,20 @@ def do_share_replica_show(cs, args): _print_share_replica(cs, replica) +@api_versions.wraps("2.47") # noqa +@cliutils.arg( + 'replica', + metavar='', + help='ID of the share replica.') +def do_share_replica_show(cs, args): + """Show details about a replica (Experimental).""" + + replica = cs.share_replicas.get(args.replica) + export_locations = cs.share_replica_export_locations.list(replica) + replica._info['export_locations'] = export_locations + _print_share_replica(cs, replica) + + @cliutils.arg( 'replica', metavar='', @@ -5059,6 +5090,55 @@ def do_share_replica_promote(cs, args): cs.share_replicas.promote(replica) +@api_versions.wraps("2.47") +@api_versions.experimental_api +@cliutils.arg( + 'replica', + metavar='', + help='ID of the share replica.') +@cliutils.arg( + '--columns', + metavar='', + type=str, + default=None, + help='Comma separated list of columns to be displayed ' + 'example --columns "id,path,replica_state".') +def do_share_replica_export_location_list(cs, args): + """List export locations of a share replica (Experimental).""" + if args.columns is not None: + list_of_keys = _split_columns(columns=args.columns) + else: + list_of_keys = [ + 'ID', + 'Availability Zone', + 'Replica State', + 'Preferred', + 'Path', + ] + replica = _find_share_replica(cs, args.replica) + export_locations = cs.share_replica_export_locations.list(replica) + cliutils.print_list(export_locations, list_of_keys) + + +@api_versions.wraps("2.47") +@api_versions.experimental_api +@cliutils.arg( + 'replica', + metavar='', + help='Name or ID of the share instance.') +@cliutils.arg( + 'export_location', + metavar='', + help='ID of the share instance export location.') +def do_share_replica_export_location_show(cs, args): + """Show details of a share replica's export location (Experimental).""" + replica = _find_share_replica(cs, args.replica) + export_location = cs.share_replica_export_locations.get( + replica, args.export_location) + view_data = export_location._info.copy() + cliutils.print_dict(view_data) + + @cliutils.arg( 'replica', metavar='', diff --git a/releasenotes/notes/bp-export-locations-replica-az-commands-03aa32c08f59c42d.yaml b/releasenotes/notes/bp-export-locations-replica-az-commands-03aa32c08f59c42d.yaml new file mode 100644 index 000000000..e9158deff --- /dev/null +++ b/releasenotes/notes/bp-export-locations-replica-az-commands-03aa32c08f59c42d.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Share replica export locations APIs are now supported within the v2 + client and the manilaclient shell. These commands are available with API + version 2.47 and beyond. \ No newline at end of file