Add support for replica export location APIs
- Add share_replica_export_locations module to the manilaclient SDK. - Add 'share-replica-export-location-list' and 'share-replica-export-location-show' commands to the manilaclient shell module. - Add export_locations attr to 'share-replica-show' command in the shell module. Change-Id: I0edb0d56f5c8f439932c18d510aad0457112b75e Depends-On: https://review.openstack.org/#/c/628069/ Implements: bp export-locations-az
This commit is contained in:
parent
6a4ad5cfee
commit
359a96ba58
@ -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 = {}
|
||||
|
@ -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 "
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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."
|
||||
|
||||
|
@ -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])
|
@ -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})
|
||||
|
||||
|
@ -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)
|
@ -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):
|
||||
|
@ -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))
|
||||
|
55
manilaclient/v2/share_replica_export_locations.py
Normal file
55
manilaclient/v2/share_replica_export_locations.py
Normal file
@ -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 "<ShareReplicaExportLocation: %s>" % 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")
|
@ -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='<replica>',
|
||||
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='<replica>',
|
||||
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='<replica>',
|
||||
@ -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='<replica>',
|
||||
help='ID of the share replica.')
|
||||
@cliutils.arg(
|
||||
'--columns',
|
||||
metavar='<columns>',
|
||||
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='<replica>',
|
||||
help='Name or ID of the share instance.')
|
||||
@cliutils.arg(
|
||||
'export_location',
|
||||
metavar='<export_location>',
|
||||
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='<replica>',
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user