Merge "Update micversion to 2.77,support share transfer between project."
This commit is contained in:
commit
d697bb5a3d
@ -27,7 +27,7 @@ from manilaclient import utils
|
|||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
MAX_VERSION = '2.75'
|
MAX_VERSION = '2.77'
|
||||||
MIN_VERSION = '2.0'
|
MIN_VERSION = '2.0'
|
||||||
DEPRECATED_VERSION = '1.0'
|
DEPRECATED_VERSION = '1.0'
|
||||||
_VERSIONED_METHOD_MAP = {}
|
_VERSIONED_METHOD_MAP = {}
|
||||||
|
@ -193,6 +193,9 @@ class Manager(utils.HookableMixin):
|
|||||||
with self.completion_cache('uuid', self.resource_class, mode="a"):
|
with self.completion_cache('uuid', self.resource_class, mode="a"):
|
||||||
return self.resource_class(self, body[response_key])
|
return self.resource_class(self, body[response_key])
|
||||||
|
|
||||||
|
def _accept(self, url, body):
|
||||||
|
resp, body = self.api.client.post(url, body=body)
|
||||||
|
|
||||||
def _delete(self, url):
|
def _delete(self, url):
|
||||||
resp, body = self.api.client.delete(url)
|
resp, body = self.api.client.delete(url)
|
||||||
|
|
||||||
|
@ -68,6 +68,17 @@ SHARE_GROUP_SNAPSHOT_SORT_KEY_VALUES = (
|
|||||||
'share_group_id',
|
'share_group_id',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SHARE_TRANSFER_SORT_KEY_VALUES = (
|
||||||
|
'id',
|
||||||
|
'resource_type',
|
||||||
|
'resource_id',
|
||||||
|
'name',
|
||||||
|
'source_project_id',
|
||||||
|
'destination_project_id',
|
||||||
|
'created_at',
|
||||||
|
'expires_at',
|
||||||
|
)
|
||||||
|
|
||||||
TASK_STATE_MIGRATION_SUCCESS = 'migration_success'
|
TASK_STATE_MIGRATION_SUCCESS = 'migration_success'
|
||||||
TASK_STATE_MIGRATION_ERROR = 'migration_error'
|
TASK_STATE_MIGRATION_ERROR = 'migration_error'
|
||||||
TASK_STATE_MIGRATION_CANCELLED = 'migration_cancelled'
|
TASK_STATE_MIGRATION_CANCELLED = 'migration_cancelled'
|
||||||
@ -119,3 +130,4 @@ GROUP_BOOL_SPECS = (
|
|||||||
|
|
||||||
REPLICA_GRADUATION_VERSION = '2.56'
|
REPLICA_GRADUATION_VERSION = '2.56'
|
||||||
REPLICA_PRE_GRADUATION_VERSION = '2.55'
|
REPLICA_PRE_GRADUATION_VERSION = '2.55'
|
||||||
|
SHARE_TRANSFER_VERSION = '2.77'
|
||||||
|
272
manilaclient/osc/v2/share_transfers.py
Normal file
272
manilaclient/osc/v2/share_transfers.py
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
# 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.common._i18n import _
|
||||||
|
from manilaclient.common.apiclient import utils as apiutils
|
||||||
|
from manilaclient.common import constants
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
TRANSFER_DETAIL_ATTRIBUTES = [
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'name',
|
||||||
|
'resource_type',
|
||||||
|
'resource_id',
|
||||||
|
'source_project_id',
|
||||||
|
'destination_project_id',
|
||||||
|
'accepted',
|
||||||
|
'expires_at'
|
||||||
|
]
|
||||||
|
|
||||||
|
TRANSFER_SUMMARY_ATTRIBUTES = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'resource_type',
|
||||||
|
'resource_id',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CreateShareTransfer(command.ShowOne):
|
||||||
|
"""Create a new share transfer."""
|
||||||
|
_description = _("Create a new share transfer")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(CreateShareTransfer, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'share',
|
||||||
|
metavar='<share>',
|
||||||
|
help='Name or ID of share to transfer.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--name',
|
||||||
|
metavar='<name>',
|
||||||
|
default=None,
|
||||||
|
help='Transfer name. Default=None.')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
share_client = self.app.client_manager.share
|
||||||
|
share = osc_utils.find_resource(share_client.shares,
|
||||||
|
parsed_args.share)
|
||||||
|
transfer = share_client.transfers.create(
|
||||||
|
share.id, name=parsed_args.name)
|
||||||
|
|
||||||
|
transfer._info.pop('links', None)
|
||||||
|
|
||||||
|
return self.dict2columns(transfer._info)
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteShareTransfer(command.Command):
|
||||||
|
"""Remove one or more transfers."""
|
||||||
|
_description = _("Remove one or more transfers")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(DeleteShareTransfer, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'transfer',
|
||||||
|
metavar='<transfer>',
|
||||||
|
nargs='+',
|
||||||
|
help='Name(s) or ID(s) of the transfer(s).')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
share_client = self.app.client_manager.share
|
||||||
|
failure_count = 0
|
||||||
|
|
||||||
|
for transfer in parsed_args.transfer:
|
||||||
|
try:
|
||||||
|
transfer_obj = apiutils.find_resource(
|
||||||
|
share_client.transfers,
|
||||||
|
transfer)
|
||||||
|
share_client.transfers.delete(transfer_obj.id)
|
||||||
|
except Exception as e:
|
||||||
|
failure_count += 1
|
||||||
|
LOG.error(_(
|
||||||
|
"Failed to delete %(transfer)s: %(e)s"),
|
||||||
|
{'transfer': transfer, 'e': e})
|
||||||
|
|
||||||
|
if failure_count > 0:
|
||||||
|
raise exceptions.CommandError(_(
|
||||||
|
"Unable to delete some or all of the specified transfers."))
|
||||||
|
|
||||||
|
|
||||||
|
class ListShareTransfer(command.Lister):
|
||||||
|
"""Lists all transfers."""
|
||||||
|
_description = _("Lists all transfers")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ListShareTransfer, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'--all-projects',
|
||||||
|
action='store_true',
|
||||||
|
help=_("Shows details for all tenants. (Admin only).")
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--name',
|
||||||
|
metavar='<name>',
|
||||||
|
default=None,
|
||||||
|
help='Filter share transfers by name. Default=None.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--id',
|
||||||
|
metavar='<id>',
|
||||||
|
default=None,
|
||||||
|
help='Filter share transfers by ID. Default=None.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--resource-type', '--resource_type',
|
||||||
|
metavar='<resource_type>',
|
||||||
|
default=None,
|
||||||
|
help='Filter share transfers by resource type, '
|
||||||
|
'which can be share. Default=None.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--resource-id', '--resource_id',
|
||||||
|
metavar='<resource_id>',
|
||||||
|
default=None,
|
||||||
|
help='Filter share transfers by resource ID. Default=None.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--source-project-id', '--source_project_id',
|
||||||
|
metavar='<source_project_id>',
|
||||||
|
default=None,
|
||||||
|
help='Filter share transfers by ID of the Project that '
|
||||||
|
'initiated the transfer. Default=None.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--limit',
|
||||||
|
metavar='<limit>',
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help='Maximum number of transfer records to '
|
||||||
|
'return. (Default=None)')
|
||||||
|
parser.add_argument(
|
||||||
|
'--offset',
|
||||||
|
metavar="<offset>",
|
||||||
|
default=None,
|
||||||
|
help='Start position of transfer records listing.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--sort-key', '--sort_key',
|
||||||
|
metavar='<sort_key>',
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help='Key to be sorted, available keys are %(keys)s. '
|
||||||
|
'Default=None.'
|
||||||
|
% {'keys': constants.SHARE_TRANSFER_SORT_KEY_VALUES})
|
||||||
|
parser.add_argument(
|
||||||
|
'--sort-dir', '--sort_dir',
|
||||||
|
metavar='<sort_dir>',
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help='Sort direction, available values are %(values)s. '
|
||||||
|
'OPTIONAL: Default=None.' % {
|
||||||
|
'values': constants.SORT_DIR_VALUES})
|
||||||
|
parser.add_argument(
|
||||||
|
'--detailed',
|
||||||
|
dest='detailed',
|
||||||
|
metavar='<0|1>',
|
||||||
|
nargs='?',
|
||||||
|
type=int,
|
||||||
|
const=1,
|
||||||
|
default=0,
|
||||||
|
help="Show detailed information about filtered share transfers.")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
share_client = self.app.client_manager.share
|
||||||
|
|
||||||
|
columns = [
|
||||||
|
'ID',
|
||||||
|
'Name',
|
||||||
|
'Resource Type',
|
||||||
|
'Resource Id'
|
||||||
|
]
|
||||||
|
|
||||||
|
if parsed_args.detailed:
|
||||||
|
columns.extend(['Created At', 'Source Project Id',
|
||||||
|
'Destination Project Id', 'Accepted',
|
||||||
|
'Expires At'])
|
||||||
|
|
||||||
|
search_opts = {
|
||||||
|
'all_tenants': parsed_args.all_projects,
|
||||||
|
'id': parsed_args.id,
|
||||||
|
'name': parsed_args.name,
|
||||||
|
'limit': parsed_args.limit,
|
||||||
|
'offset': parsed_args.offset,
|
||||||
|
'resource_type': parsed_args.resource_type,
|
||||||
|
'resource_id': parsed_args.resource_id,
|
||||||
|
'source_project_id': parsed_args.source_project_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
transfers = share_client.transfers.list(
|
||||||
|
detailed=parsed_args.detailed, search_opts=search_opts,
|
||||||
|
sort_key=parsed_args.sort_key, sort_dir=parsed_args.sort_dir)
|
||||||
|
|
||||||
|
return (columns, (osc_utils.get_item_properties
|
||||||
|
(m, columns) for m in transfers))
|
||||||
|
|
||||||
|
|
||||||
|
class ShowShareTransfer(command.ShowOne):
|
||||||
|
"""Show details about a share transfer."""
|
||||||
|
_description = _("Show details about a share transfer")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ShowShareTransfer, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'transfer',
|
||||||
|
metavar='<transfer>',
|
||||||
|
help=_('Name or ID of transfer to show.'))
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
share_client = self.app.client_manager.share
|
||||||
|
|
||||||
|
transfer = apiutils.find_resource(
|
||||||
|
share_client.transfers,
|
||||||
|
parsed_args.transfer)
|
||||||
|
|
||||||
|
return (TRANSFER_DETAIL_ATTRIBUTES, osc_utils.get_dict_properties(
|
||||||
|
transfer._info, TRANSFER_DETAIL_ATTRIBUTES))
|
||||||
|
|
||||||
|
|
||||||
|
class AcceptShareTransfer(command.Command):
|
||||||
|
"""Accepts a share transfer."""
|
||||||
|
_description = _("Accepts a share transfer")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(AcceptShareTransfer, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'transfer',
|
||||||
|
metavar='<transfer>',
|
||||||
|
help='ID of transfer to accept.')
|
||||||
|
parser.add_argument(
|
||||||
|
'auth_key',
|
||||||
|
metavar='<auth_key>',
|
||||||
|
help='Authentication key of transfer to accept.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--clear-rules',
|
||||||
|
'--clear_rules',
|
||||||
|
dest='clear_rules',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help="Whether manila should clean up the access rules after the "
|
||||||
|
"transfer is complete. (Default=False)")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
share_client = self.app.client_manager.share
|
||||||
|
|
||||||
|
share_client.transfers.accept(
|
||||||
|
parsed_args.transfer, parsed_args.auth_key,
|
||||||
|
clear_access_rules=parsed_args.clear_rules)
|
@ -368,6 +368,36 @@ class BaseTestCase(base.ClientTestBase):
|
|||||||
client.restore_share(shares_to_restore,
|
client.restore_share(shares_to_restore,
|
||||||
microversion=microversion)
|
microversion=microversion)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_share_transfer(cls, share_id, name=None,
|
||||||
|
client=None, microversion=None):
|
||||||
|
client = client or cls.get_admin_client()
|
||||||
|
return client.create_share_transfer(share_id, name=name,
|
||||||
|
microversion=microversion)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_share_transfer(cls, transfer, client=None,
|
||||||
|
microversion=None):
|
||||||
|
client = client or cls.get_admin_client()
|
||||||
|
client.delete_share_transfer(transfer, microversion=microversion)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_share_transfer(cls, transfer, client=None, microversion=None):
|
||||||
|
client = client or cls.get_admin_client()
|
||||||
|
return client.get_share_transfer(transfer, microversion=microversion)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_share_transfer(cls, client=None, microversion=None):
|
||||||
|
client = client or cls.get_admin_client()
|
||||||
|
return client.list_share_transfer(microversion=microversion)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def accept_share_transfer(cls, transfer, auth_key,
|
||||||
|
client=None, microversion=None):
|
||||||
|
client = client or cls.get_admin_client()
|
||||||
|
client.accept_share_transfer(transfer, auth_key,
|
||||||
|
microversion=microversion)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _determine_share_network_to_use(cls, client, share_type,
|
def _determine_share_network_to_use(cls, client, share_type,
|
||||||
microversion=None):
|
microversion=None):
|
||||||
|
@ -37,6 +37,7 @@ SHARE_NETWORK_SUBNET = 'share_network_subnet'
|
|||||||
SHARE_SERVER = 'share_server'
|
SHARE_SERVER = 'share_server'
|
||||||
SNAPSHOT = 'snapshot'
|
SNAPSHOT = 'snapshot'
|
||||||
SHARE_REPLICA = 'share_replica'
|
SHARE_REPLICA = 'share_replica'
|
||||||
|
TRANSFER = 'transfer'
|
||||||
|
|
||||||
|
|
||||||
def not_found_wrapper(f):
|
def not_found_wrapper(f):
|
||||||
@ -142,6 +143,8 @@ class ManilaCLIClient(base.CLIClient):
|
|||||||
func = self.is_message_deleted
|
func = self.is_message_deleted
|
||||||
elif res_type == SHARE_REPLICA:
|
elif res_type == SHARE_REPLICA:
|
||||||
func = self.is_share_replica_deleted
|
func = self.is_share_replica_deleted
|
||||||
|
elif res_type == TRANSFER:
|
||||||
|
func = self.is_share_transfer_deleted
|
||||||
else:
|
else:
|
||||||
raise exceptions.InvalidResource(message=res_type)
|
raise exceptions.InvalidResource(message=res_type)
|
||||||
|
|
||||||
@ -919,6 +922,55 @@ class ManilaCLIClient(base.CLIClient):
|
|||||||
cmd += '%s ' % share
|
cmd += '%s ' % share
|
||||||
return self.manila(cmd, microversion=microversion)
|
return self.manila(cmd, microversion=microversion)
|
||||||
|
|
||||||
|
def create_share_transfer(self, share_id, name=None,
|
||||||
|
microversion=None):
|
||||||
|
"""Create a share transfer.
|
||||||
|
|
||||||
|
:param share_id: ID of share.
|
||||||
|
":param name: name of transfer.
|
||||||
|
"""
|
||||||
|
cmd = 'share-transfer-create %s ' % share_id
|
||||||
|
if name:
|
||||||
|
cmd += '--name %s' % name
|
||||||
|
transfer_raw = self.manila(cmd, microversion=microversion)
|
||||||
|
transfer = output_parser.details(transfer_raw)
|
||||||
|
return transfer
|
||||||
|
|
||||||
|
def delete_share_transfer(self, transfer, microversion=None):
|
||||||
|
"""Delete a share transfer.
|
||||||
|
|
||||||
|
:param transfer: ID or name of share transfer.
|
||||||
|
"""
|
||||||
|
cmd = 'share-transfer-delete %s ' % transfer
|
||||||
|
self.manila(cmd, microversion=microversion)
|
||||||
|
|
||||||
|
def get_share_transfer(self, transfer, microversion=None):
|
||||||
|
"""Get a share transfer.
|
||||||
|
|
||||||
|
:param transfer: ID or name of share transfer.
|
||||||
|
"""
|
||||||
|
cmd = 'share-transfer-show %s ' % transfer
|
||||||
|
transfer_raw = self.manila(cmd, microversion=microversion)
|
||||||
|
transfer = output_parser.details(transfer_raw)
|
||||||
|
return transfer
|
||||||
|
|
||||||
|
def list_share_transfer(self, microversion=None):
|
||||||
|
"""Get a share transfer."""
|
||||||
|
|
||||||
|
cmd = 'share-transfer-list '
|
||||||
|
transfer_raw = self.manila(cmd, microversion=microversion)
|
||||||
|
transfers = utils.listing(transfer_raw)
|
||||||
|
return transfers
|
||||||
|
|
||||||
|
def accept_share_transfer(self, transfer, auth_key,
|
||||||
|
microversion=None):
|
||||||
|
"""Accept a share transfer.
|
||||||
|
|
||||||
|
:param transfer: ID or name of share transfer.
|
||||||
|
"""
|
||||||
|
cmd = 'share-transfer-accept %s %s' % (transfer, auth_key)
|
||||||
|
self.manila(cmd, microversion=microversion)
|
||||||
|
|
||||||
def list_shares(self, all_tenants=False, is_soft_deleted=False,
|
def list_shares(self, all_tenants=False, is_soft_deleted=False,
|
||||||
filters=None, columns=None, is_public=False,
|
filters=None, columns=None, is_public=False,
|
||||||
microversion=None):
|
microversion=None):
|
||||||
@ -1003,6 +1055,26 @@ class ManilaCLIClient(base.CLIClient):
|
|||||||
SHARE, res_id=share, interval=5, timeout=300,
|
SHARE, res_id=share, interval=5, timeout=300,
|
||||||
microversion=microversion)
|
microversion=microversion)
|
||||||
|
|
||||||
|
def is_share_transfer_deleted(self, transfer, microversion=None):
|
||||||
|
"""Says whether transfer is deleted or not.
|
||||||
|
|
||||||
|
:param transfer: str -- Name or ID of transfer
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.get_transfer(transfer, microversion=microversion)
|
||||||
|
return False
|
||||||
|
except tempest_lib_exc.NotFound:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def wait_for_transfer_deletion(self, transfer, microversion=None):
|
||||||
|
"""Wait for transfer deletion by its Name or ID.
|
||||||
|
|
||||||
|
:param transfer: str -- Name or ID of transfer.
|
||||||
|
"""
|
||||||
|
self.wait_for_resource_deletion(
|
||||||
|
SHARE, res_id=transfer, interval=5, timeout=300,
|
||||||
|
microversion=microversion)
|
||||||
|
|
||||||
def wait_for_share_soft_deletion(self, share_id, microversion=None):
|
def wait_for_share_soft_deletion(self, share_id, microversion=None):
|
||||||
body = self.get_share(share_id, microversion=microversion)
|
body = self.get_share(share_id, microversion=microversion)
|
||||||
is_soft_deleted = body['is_soft_deleted']
|
is_soft_deleted = body['is_soft_deleted']
|
||||||
|
@ -302,6 +302,14 @@ class OSCClientTestBase(base.ClientTestBase):
|
|||||||
|
|
||||||
return snapshot_object
|
return snapshot_object
|
||||||
|
|
||||||
|
def create_share_transfer(self, share, name=None, client=None):
|
||||||
|
|
||||||
|
name = name or data_utils.rand_name('autotest_share_transfer_name')
|
||||||
|
cmd = (f'transfer create {share} --name {name} ')
|
||||||
|
transfer_object = self.dict_result('share', cmd, client=client)
|
||||||
|
|
||||||
|
return transfer_object
|
||||||
|
|
||||||
def create_share_network(self, neutron_net_id=None,
|
def create_share_network(self, neutron_net_id=None,
|
||||||
neutron_subnet_id=None, name=None,
|
neutron_subnet_id=None, name=None,
|
||||||
description=None, availability_zone=None,
|
description=None, availability_zone=None,
|
||||||
|
85
manilaclient/tests/functional/osc/test_share_transfers.py
Normal file
85
manilaclient/tests/functional/osc/test_share_transfers.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# Copyright (c) 2022 China Telecom Digital Intelligence.
|
||||||
|
# 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.tests.functional.osc import base
|
||||||
|
|
||||||
|
|
||||||
|
class TransfersCLITest(base.OSCClientTestBase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TransfersCLITest, self).setUp()
|
||||||
|
self.share_type = self.create_share_type()
|
||||||
|
|
||||||
|
def test_transfer_create_list_show_delete(self):
|
||||||
|
share = self.create_share(share_type=self.share_type['name'],
|
||||||
|
wait_for_status='available',
|
||||||
|
client=self.user_client)
|
||||||
|
# create share transfer
|
||||||
|
self.create_share_transfer(share['id'], name='transfer_test')
|
||||||
|
self._wait_for_object_status('share', share['id'], 'awaiting_transfer')
|
||||||
|
|
||||||
|
# Get all transfers
|
||||||
|
transfers = self.listing_result(
|
||||||
|
'share', 'transfer list', client=self.user_client)
|
||||||
|
# We must have at least one transfer
|
||||||
|
self.assertTrue(len(transfers) > 0)
|
||||||
|
self.assertTableStruct(transfers, [
|
||||||
|
'ID',
|
||||||
|
'Name',
|
||||||
|
'Resource Type',
|
||||||
|
'Resource Id',
|
||||||
|
])
|
||||||
|
|
||||||
|
# grab the transfer we created
|
||||||
|
transfer = [transfer for transfer in transfers
|
||||||
|
if transfer['Resource Id'] == share['id']]
|
||||||
|
self.assertEqual(1, len(transfer))
|
||||||
|
|
||||||
|
show_transfer = self.dict_result('share',
|
||||||
|
f'transfer show {transfer[0]["ID"]}')
|
||||||
|
self.assertEqual(transfer[0]['ID'], show_transfer['id'])
|
||||||
|
expected_keys = (
|
||||||
|
'id', 'created_at', 'name', 'resource_type', 'resource_id',
|
||||||
|
'source_project_id', 'destination_project_id', 'accepted',
|
||||||
|
'expires_at',
|
||||||
|
)
|
||||||
|
for key in expected_keys:
|
||||||
|
self.assertIn(key, show_transfer)
|
||||||
|
|
||||||
|
# filtering by Resource ID
|
||||||
|
filtered_transfers = self.listing_result(
|
||||||
|
'share',
|
||||||
|
f'transfer list --resource-id {share["id"]}',
|
||||||
|
client=self.user_client)
|
||||||
|
self.assertEqual(1, len(filtered_transfers))
|
||||||
|
self.assertEqual(show_transfer['resource_id'], share["id"])
|
||||||
|
|
||||||
|
# finally delete transfer and share
|
||||||
|
self.openstack(f'share transfer delete {show_transfer["id"]}')
|
||||||
|
self._wait_for_object_status('share', share['id'], 'available')
|
||||||
|
|
||||||
|
def test_transfer_accept(self):
|
||||||
|
share = self.create_share(share_type=self.share_type['name'],
|
||||||
|
wait_for_status='available',
|
||||||
|
client=self.user_client)
|
||||||
|
# create share transfer
|
||||||
|
transfer = self.create_share_transfer(share['id'],
|
||||||
|
name='transfer_test')
|
||||||
|
self._wait_for_object_status('share', share['id'], 'awaiting_transfer')
|
||||||
|
|
||||||
|
# accept share transfer
|
||||||
|
self.openstack(
|
||||||
|
f'share transfer accept {transfer["id"]} {transfer["auth_key"]}')
|
||||||
|
self._wait_for_object_status('share', share['id'], 'available')
|
95
manilaclient/tests/functional/test_share_transfers.py
Normal file
95
manilaclient/tests/functional/test_share_transfers.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# Copyright (c) 2022 China Telecom Digital Intelligence.
|
||||||
|
# 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 tempest.lib.common.utils import data_utils
|
||||||
|
|
||||||
|
from manilaclient.tests.functional import base
|
||||||
|
|
||||||
|
|
||||||
|
class ShareTransferTests(base.BaseTestCase):
|
||||||
|
"""Check of base share transfers command"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ShareTransferTests, self).setUp()
|
||||||
|
self.share_type = self.create_share_type(
|
||||||
|
name=data_utils.rand_name('test_share_type'),
|
||||||
|
driver_handles_share_servers=False)
|
||||||
|
|
||||||
|
def test_transfer_create_list_show_delete(self):
|
||||||
|
"""Create, list, show and delete a share transfer"""
|
||||||
|
self.skip_if_microversion_not_supported('2.77')
|
||||||
|
share = self.create_share(
|
||||||
|
share_protocol='nfs',
|
||||||
|
size=1,
|
||||||
|
name=data_utils.rand_name('autotest_share_name'),
|
||||||
|
client=self.user_client,
|
||||||
|
share_type=self.share_type['ID'],
|
||||||
|
use_wait_option=True)
|
||||||
|
self.assertEqual("available", share['status'])
|
||||||
|
# create share transfer
|
||||||
|
transfer = self.create_share_transfer(share['id'],
|
||||||
|
name='test_share_transfer')
|
||||||
|
self.assertIn('auth_key', transfer)
|
||||||
|
|
||||||
|
# list share transfers
|
||||||
|
transfers = self.list_share_transfer()
|
||||||
|
# We must have at least one transfer
|
||||||
|
self.assertTrue(len(transfers) > 0)
|
||||||
|
|
||||||
|
# show the share transfer
|
||||||
|
transfer_show = self.get_share_transfer(transfer['id'])
|
||||||
|
self.assertEqual(transfer_show['name'], transfer['name'])
|
||||||
|
self.assertNotIn('auth_key', transfer_show)
|
||||||
|
|
||||||
|
# delete share transfer
|
||||||
|
self.delete_share_transfer(transfer['id'])
|
||||||
|
self.user_client.wait_for_transfer_deletion(transfer['id'])
|
||||||
|
share = self.user_client.get_share(share['id'])
|
||||||
|
self.assertEqual("available", share['status'])
|
||||||
|
# finally delete the share
|
||||||
|
self.user_client.delete_share(share['id'])
|
||||||
|
self.user_client.wait_for_share_deletion(share['id'])
|
||||||
|
|
||||||
|
def test_transfer_accept(self):
|
||||||
|
"""Show share transfer accept"""
|
||||||
|
self.skip_if_microversion_not_supported('2.77')
|
||||||
|
share = self.create_share(
|
||||||
|
share_protocol='nfs',
|
||||||
|
size=1,
|
||||||
|
name=data_utils.rand_name('autotest_share_name'),
|
||||||
|
client=self.user_client,
|
||||||
|
share_type=self.share_type['ID'],
|
||||||
|
use_wait_option=True)
|
||||||
|
self.assertEqual("available", share['status'])
|
||||||
|
# create share transfer
|
||||||
|
transfer = self.create_share_transfer(share['id'],
|
||||||
|
name='test_share_transfer')
|
||||||
|
share = self.user_client.get_share(share['id'])
|
||||||
|
transfer_id = transfer['id']
|
||||||
|
auth_key = transfer['auth_key']
|
||||||
|
self.assertEqual("share", transfer['resource_type'])
|
||||||
|
self.assertEqual('test_share_transfer', transfer['name'])
|
||||||
|
self.assertEqual("awaiting_transfer", share['status'])
|
||||||
|
|
||||||
|
# accept the share transfer
|
||||||
|
self.accept_share_transfer(transfer_id, auth_key)
|
||||||
|
# after accept complete, the transfer will be deleted.
|
||||||
|
self.user_client.wait_for_transfer_deletion(transfer_id)
|
||||||
|
share = self.user_client.get_share(share['id'])
|
||||||
|
# check share status roll back to available
|
||||||
|
self.assertEqual("available", share['status'])
|
||||||
|
# finally delete the share
|
||||||
|
self.user_client.delete_share(share['id'])
|
||||||
|
self.user_client.wait_for_share_deletion(share['id'])
|
@ -30,6 +30,7 @@ class FakeShareClient(object):
|
|||||||
self.auth_token = kwargs['token']
|
self.auth_token = kwargs['token']
|
||||||
self.management_url = kwargs['endpoint']
|
self.management_url = kwargs['endpoint']
|
||||||
self.shares = mock.Mock()
|
self.shares = mock.Mock()
|
||||||
|
self.transfers = mock.Mock()
|
||||||
self.share_access_rules = mock.Mock()
|
self.share_access_rules = mock.Mock()
|
||||||
self.share_groups = mock.Mock()
|
self.share_groups = mock.Mock()
|
||||||
self.share_types = mock.Mock()
|
self.share_types = mock.Mock()
|
||||||
@ -585,6 +586,64 @@ class FakeShareSnapshot(object):
|
|||||||
return share_snapshots
|
return share_snapshots
|
||||||
|
|
||||||
|
|
||||||
|
class FakeShareTransfer(object):
|
||||||
|
"""Fake a share transfer"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_one_transfer(attrs=None, methods=None):
|
||||||
|
"""Create a fake share transfer
|
||||||
|
|
||||||
|
:param Dictionary attrs:
|
||||||
|
A dictionary with all attributes
|
||||||
|
:return:
|
||||||
|
A FakeResource object, with id, resource and so on
|
||||||
|
"""
|
||||||
|
|
||||||
|
attrs = attrs or {}
|
||||||
|
methods = methods or {}
|
||||||
|
now_time = datetime.datetime.now()
|
||||||
|
delta_time = now_time + datetime.timedelta(minutes=5)
|
||||||
|
|
||||||
|
share_transfer = {
|
||||||
|
'accepted': 'False',
|
||||||
|
'auth_key': 'auth-key-' + uuid.uuid4().hex,
|
||||||
|
'created_at': now_time.isoformat(),
|
||||||
|
'destination_project_id': None,
|
||||||
|
'expires_at': delta_time.isoformat(),
|
||||||
|
'id': 'transfer-id-' + uuid.uuid4().hex,
|
||||||
|
'name': 'name-' + uuid.uuid4().hex,
|
||||||
|
'resource_id': 'resource-id-' + uuid.uuid4().hex,
|
||||||
|
'resource_type': 'share',
|
||||||
|
'source_project_id': 'source-project-id-' + uuid.uuid4().hex
|
||||||
|
}
|
||||||
|
|
||||||
|
share_transfer.update(attrs)
|
||||||
|
share_transfer = osc_fakes.FakeResource(info=copy.deepcopy(
|
||||||
|
share_transfer),
|
||||||
|
methods=methods,
|
||||||
|
loaded=True)
|
||||||
|
return share_transfer
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_share_transfers(attrs=None, count=2):
|
||||||
|
"""Create multiple fake transfers.
|
||||||
|
|
||||||
|
:param Dictionary attrs:
|
||||||
|
A dictionary with all attributes
|
||||||
|
:param Integer count:
|
||||||
|
The number of share transfers to be faked
|
||||||
|
:return:
|
||||||
|
A list of FakeResource objects
|
||||||
|
"""
|
||||||
|
|
||||||
|
share_transfers = []
|
||||||
|
for n in range(0, count):
|
||||||
|
share_transfers.append(
|
||||||
|
FakeShareSnapshot.create_one_snapshot(attrs))
|
||||||
|
|
||||||
|
return share_transfers
|
||||||
|
|
||||||
|
|
||||||
class FakeSnapshotAccessRule(object):
|
class FakeSnapshotAccessRule(object):
|
||||||
"""Fake one or more snapshot access rules"""
|
"""Fake one or more snapshot access rules"""
|
||||||
|
|
||||||
|
289
manilaclient/tests/unit/osc/v2/test_share_transfers.py
Normal file
289
manilaclient/tests/unit/osc/v2/test_share_transfers.py
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
# Copyright (c) 2022 China Telecom Digital Intelligence.
|
||||||
|
# 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 osc_lib import exceptions
|
||||||
|
from osc_lib import utils as oscutils
|
||||||
|
|
||||||
|
from manilaclient import api_versions
|
||||||
|
from manilaclient.osc.v2 import share_transfers as osc_share_transfers
|
||||||
|
from manilaclient.tests.unit.osc import osc_utils
|
||||||
|
from manilaclient.tests.unit.osc.v2 import fakes as manila_fakes
|
||||||
|
|
||||||
|
COLUMNS = [
|
||||||
|
'ID',
|
||||||
|
'Name',
|
||||||
|
'Resource Type',
|
||||||
|
'Resource Id',
|
||||||
|
'Created At',
|
||||||
|
'Source Project Id',
|
||||||
|
'Destination Project Id',
|
||||||
|
'Accepted',
|
||||||
|
'Expires At'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TestShareTransfer(manila_fakes.TestShare):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestShareTransfer, self).setUp()
|
||||||
|
|
||||||
|
self.shares_mock = self.app.client_manager.share.shares
|
||||||
|
self.shares_mock.reset_mock()
|
||||||
|
|
||||||
|
self.transfers_mock = self.app.client_manager.share.transfers
|
||||||
|
self.transfers_mock.reset_mock()
|
||||||
|
|
||||||
|
self.app.client_manager.share.api_version = api_versions.APIVersion(
|
||||||
|
api_versions.MAX_VERSION)
|
||||||
|
|
||||||
|
|
||||||
|
class TestShareTransferCreate(TestShareTransfer):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestShareTransferCreate, self).setUp()
|
||||||
|
|
||||||
|
self.share = manila_fakes.FakeShare.create_one_share()
|
||||||
|
self.shares_mock.create.return_value = self.share
|
||||||
|
|
||||||
|
self.shares_mock.get.return_value = self.share
|
||||||
|
|
||||||
|
self.share_transfer = (
|
||||||
|
manila_fakes.FakeShareTransfer.create_one_transfer())
|
||||||
|
self.transfers_mock.get.return_value = self.share_transfer
|
||||||
|
self.transfers_mock.create.return_value = self.share_transfer
|
||||||
|
|
||||||
|
self.cmd = osc_share_transfers.CreateShareTransfer(self.app, None)
|
||||||
|
|
||||||
|
self.data = tuple(self.share_transfer._info.values())
|
||||||
|
self.columns = tuple(self.share_transfer._info.keys())
|
||||||
|
|
||||||
|
def test_share_transfer_create_missing_args(self):
|
||||||
|
arglist = []
|
||||||
|
verifylist = []
|
||||||
|
|
||||||
|
self.assertRaises(osc_utils.ParserException,
|
||||||
|
self.check_parser, self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
def test_share_transfer_create_required_args(self):
|
||||||
|
arglist = [
|
||||||
|
self.share.id
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('share', self.share.id)
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.transfers_mock.create.assert_called_with(
|
||||||
|
self.share.id,
|
||||||
|
name=None
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertCountEqual(self.columns, columns)
|
||||||
|
self.assertCountEqual(self.data, data)
|
||||||
|
|
||||||
|
|
||||||
|
class TestShareTransferDelete(TestShareTransfer):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestShareTransferDelete, self).setUp()
|
||||||
|
|
||||||
|
self.transfer = (
|
||||||
|
manila_fakes.FakeShareTransfer.create_one_transfer())
|
||||||
|
|
||||||
|
self.transfers_mock.get.return_value = self.transfer
|
||||||
|
|
||||||
|
self.cmd = osc_share_transfers.DeleteShareTransfer(self.app, None)
|
||||||
|
|
||||||
|
def test_share_transfer_delete_missing_args(self):
|
||||||
|
arglist = []
|
||||||
|
verifylist = []
|
||||||
|
|
||||||
|
self.assertRaises(osc_utils.ParserException,
|
||||||
|
self.check_parser, self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
def test_share_transfer_delete(self):
|
||||||
|
arglist = [
|
||||||
|
self.transfer.id
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('transfer', [self.transfer.id])
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.transfers_mock.delete.assert_called_with(self.transfer.id)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_share_transfer_delete_multiple(self):
|
||||||
|
transfers = (
|
||||||
|
manila_fakes.FakeShareTransfer.create_share_transfers(
|
||||||
|
count=2))
|
||||||
|
arglist = [
|
||||||
|
transfers[0].id,
|
||||||
|
transfers[1].id
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('transfer', [transfers[0].id, transfers[1].id])
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.assertEqual(self.transfers_mock.delete.call_count,
|
||||||
|
len(transfers))
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_share_transfer_delete_exception(self):
|
||||||
|
arglist = [
|
||||||
|
self.transfer.id
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('transfer', [self.transfer.id])
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
self.transfers_mock.delete.side_effect = exceptions.CommandError()
|
||||||
|
self.assertRaises(exceptions.CommandError,
|
||||||
|
self.cmd.take_action,
|
||||||
|
parsed_args)
|
||||||
|
|
||||||
|
|
||||||
|
class TestShareTransferShow(TestShareTransfer):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestShareTransferShow, self).setUp()
|
||||||
|
|
||||||
|
self.transfer = (
|
||||||
|
manila_fakes.FakeShareTransfer.create_one_transfer())
|
||||||
|
self.transfers_mock.get.return_value = self.transfer
|
||||||
|
|
||||||
|
self.cmd = osc_share_transfers.ShowShareTransfer(self.app, None)
|
||||||
|
|
||||||
|
self.data = self.transfer._info.values()
|
||||||
|
self.transfer._info.pop('auth_key')
|
||||||
|
self.columns = self.transfer._info.keys()
|
||||||
|
|
||||||
|
def test_share_transfer_show_missing_args(self):
|
||||||
|
arglist = []
|
||||||
|
verifylist = []
|
||||||
|
|
||||||
|
self.assertRaises(osc_utils.ParserException,
|
||||||
|
self.check_parser, self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
def test_share_transfer_show(self):
|
||||||
|
arglist = [
|
||||||
|
self.transfer.id
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('transfer', self.transfer.id)
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
self.transfers_mock.get.assert_called_with(self.transfer.id)
|
||||||
|
self.assertCountEqual(self.columns, columns)
|
||||||
|
self.assertCountEqual(self.data, data)
|
||||||
|
|
||||||
|
|
||||||
|
class TestShareTransferList(TestShareTransfer):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestShareTransferList, self).setUp()
|
||||||
|
|
||||||
|
self.transfers = (
|
||||||
|
manila_fakes.FakeShareTransfer.create_share_transfers(
|
||||||
|
count=2))
|
||||||
|
|
||||||
|
self.transfers_mock.list.return_value = self.transfers
|
||||||
|
|
||||||
|
self.values = (oscutils.get_dict_properties(
|
||||||
|
m._info, COLUMNS) for m in self.transfers)
|
||||||
|
|
||||||
|
self.cmd = osc_share_transfers.ListShareTransfer(self.app, None)
|
||||||
|
|
||||||
|
def test_list_transfers(self):
|
||||||
|
arglist = [
|
||||||
|
'--detailed'
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('detailed', True)
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.transfers_mock.list.assert_called_with(
|
||||||
|
detailed=1,
|
||||||
|
search_opts={
|
||||||
|
'all_tenants': False,
|
||||||
|
'id': None,
|
||||||
|
'name': None,
|
||||||
|
'limit': None,
|
||||||
|
'offset': None,
|
||||||
|
'resource_type': None,
|
||||||
|
'resource_id': None,
|
||||||
|
'source_project_id': None},
|
||||||
|
sort_key=None,
|
||||||
|
sort_dir=None
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(COLUMNS, columns)
|
||||||
|
self.assertEqual(list(self.values), list(data))
|
||||||
|
|
||||||
|
|
||||||
|
class TestShareTransferAccept(TestShareTransfer):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestShareTransferAccept, self).setUp()
|
||||||
|
|
||||||
|
self.transfer = (
|
||||||
|
manila_fakes.FakeShareTransfer.create_one_transfer())
|
||||||
|
|
||||||
|
self.transfers_mock.get.return_value = self.transfer
|
||||||
|
|
||||||
|
self.cmd = osc_share_transfers.AcceptShareTransfer(self.app, None)
|
||||||
|
|
||||||
|
def test_share_transfer_accept_missing_args(self):
|
||||||
|
arglist = []
|
||||||
|
verifylist = []
|
||||||
|
|
||||||
|
self.assertRaises(osc_utils.ParserException,
|
||||||
|
self.check_parser, self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
def test_share_transfer_accept(self):
|
||||||
|
arglist = [
|
||||||
|
self.transfer.id,
|
||||||
|
self.transfer.auth_key
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('transfer', self.transfer.id),
|
||||||
|
('auth_key', self.transfer.auth_key)
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.transfers_mock.accept.assert_called_with(self.transfer.id,
|
||||||
|
self.transfer.auth_key,
|
||||||
|
clear_access_rules=False)
|
||||||
|
self.assertIsNone(result)
|
@ -1317,6 +1317,19 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
|
|||||||
'request_id': 'req-936666d2-4c8f-4e41-9ac9-237b43f8b848',
|
'request_id': 'req-936666d2-4c8f-4e41-9ac9-237b43f8b848',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fake_transfer = {
|
||||||
|
"id": "f21c72c4-2b77-445b-aa12-e8d1b44163a2",
|
||||||
|
"created_at": "2022-09-06T08:17:43.629495",
|
||||||
|
"name": "test_transfer",
|
||||||
|
"resource_type": "share",
|
||||||
|
"resource_id": "29476819-28a9-4b1a-a21d-3b2d203025a0",
|
||||||
|
"auth_key": "406a2d67cdb09afe",
|
||||||
|
"source_project_id": "714198c7ac5e45a4b785de732ea4695d",
|
||||||
|
"destination_project_id": None,
|
||||||
|
"accepted": False,
|
||||||
|
"expires_at": None,
|
||||||
|
}
|
||||||
|
|
||||||
def get_messages(self, **kw):
|
def get_messages(self, **kw):
|
||||||
messages = {
|
messages = {
|
||||||
'messages': [self.fake_message],
|
'messages': [self.fake_message],
|
||||||
@ -1333,6 +1346,25 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
|
|||||||
def delete_messages_5678(self, **kw):
|
def delete_messages_5678(self, **kw):
|
||||||
return 202, {}, None
|
return 202, {}, None
|
||||||
|
|
||||||
|
def post_share_transfers(self, **kw):
|
||||||
|
transfer = {'transfer': self.fake_transfer}
|
||||||
|
return 202, {}, transfer
|
||||||
|
|
||||||
|
def get_share_transfers_5678(self, **kw):
|
||||||
|
transfer = {'transfer': self.fake_transfer}
|
||||||
|
return 202, {}, transfer
|
||||||
|
|
||||||
|
def get_share_transfers_detail(self, **kw):
|
||||||
|
transfer = {'transfers': [self.fake_transfer]}
|
||||||
|
return 202, {}, transfer
|
||||||
|
|
||||||
|
def delete_share_transfers_5678(self, **kw):
|
||||||
|
return 202, {}, None
|
||||||
|
|
||||||
|
def post_share_transfers_5678_accept(self, **kw):
|
||||||
|
transfer = {'transfer': self.fake_transfer}
|
||||||
|
return 202, {}, transfer
|
||||||
|
|
||||||
|
|
||||||
def fake_create(url, body, response_key):
|
def fake_create(url, body, response_key):
|
||||||
return {'url': url, 'body': body, 'resp_key': response_key}
|
return {'url': url, 'body': body, 'resp_key': response_key}
|
||||||
|
50
manilaclient/tests/unit/v2/test_share_transfers.py
Normal file
50
manilaclient/tests/unit/v2/test_share_transfers.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Copyright (c) 2022 China Telecom Digital Intelligence.
|
||||||
|
# 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.tests.unit import utils
|
||||||
|
from manilaclient.tests.unit.v2 import fakes
|
||||||
|
|
||||||
|
TRANSFER_URL = 'share-transfers'
|
||||||
|
cs = fakes.FakeClient(api_versions.APIVersion('2.77'))
|
||||||
|
|
||||||
|
|
||||||
|
class ShareTransfersTest(utils.TestCase):
|
||||||
|
|
||||||
|
def test_create(self):
|
||||||
|
cs.transfers.create('1234')
|
||||||
|
cs.assert_called('POST', '/%s' % TRANSFER_URL,
|
||||||
|
body={'transfer': {'share_id': '1234',
|
||||||
|
'name': None}})
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
transfer_id = '5678'
|
||||||
|
cs.transfers.get(transfer_id)
|
||||||
|
cs.assert_called('GET', '/%s/%s' % (TRANSFER_URL, transfer_id))
|
||||||
|
|
||||||
|
def test_list(self):
|
||||||
|
cs.transfers.list()
|
||||||
|
cs.assert_called('GET', '/%s/detail' % TRANSFER_URL)
|
||||||
|
|
||||||
|
def test_delete(self):
|
||||||
|
cs.transfers.delete('5678')
|
||||||
|
cs.assert_called('DELETE', '/%s/5678' % TRANSFER_URL)
|
||||||
|
|
||||||
|
def test_accept(self):
|
||||||
|
transfer_id = '5678'
|
||||||
|
auth_key = '12345'
|
||||||
|
cs.transfers.accept(transfer_id, auth_key)
|
||||||
|
cs.assert_called('POST',
|
||||||
|
'/%s/%s/accept' % (TRANSFER_URL, transfer_id))
|
@ -43,6 +43,7 @@ from manilaclient.v2 import share_snapshot_export_locations
|
|||||||
from manilaclient.v2 import share_snapshot_instance_export_locations
|
from manilaclient.v2 import share_snapshot_instance_export_locations
|
||||||
from manilaclient.v2 import share_snapshot_instances
|
from manilaclient.v2 import share_snapshot_instances
|
||||||
from manilaclient.v2 import share_snapshots
|
from manilaclient.v2 import share_snapshots
|
||||||
|
from manilaclient.v2 import share_transfers
|
||||||
from manilaclient.v2 import share_type_access
|
from manilaclient.v2 import share_type_access
|
||||||
from manilaclient.v2 import share_types
|
from manilaclient.v2 import share_types
|
||||||
from manilaclient.v2 import shares
|
from manilaclient.v2 import shares
|
||||||
@ -189,6 +190,7 @@ class Client(object):
|
|||||||
self.availability_zones = availability_zones.AvailabilityZoneManager(
|
self.availability_zones = availability_zones.AvailabilityZoneManager(
|
||||||
self)
|
self)
|
||||||
self.limits = limits.LimitsManager(self)
|
self.limits = limits.LimitsManager(self)
|
||||||
|
self.transfers = share_transfers.ShareTransferManager(self)
|
||||||
self.messages = messages.MessageManager(self)
|
self.messages = messages.MessageManager(self)
|
||||||
self.services = services.ServiceManager(self)
|
self.services = services.ServiceManager(self)
|
||||||
self.security_services = security_services.SecurityServiceManager(self)
|
self.security_services = security_services.SecurityServiceManager(self)
|
||||||
|
121
manilaclient/v2/share_transfers.py
Normal file
121
manilaclient/v2/share_transfers.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# Copyright (c) 2022 China Telecom Digital Intelligence.
|
||||||
|
# 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 import constants
|
||||||
|
|
||||||
|
|
||||||
|
class ShareTransfer(base.Resource):
|
||||||
|
"""Transfer a share from one tenant to another"""
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<ShareTransfer: %s>" % self.id
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
"""Delete this share transfer."""
|
||||||
|
return self.manager.delete(self)
|
||||||
|
|
||||||
|
|
||||||
|
class ShareTransferManager(base.ManagerWithFind):
|
||||||
|
"""Manage :class:`ShareTransfer` resources."""
|
||||||
|
resource_class = ShareTransfer
|
||||||
|
|
||||||
|
@api_versions.wraps(constants.SHARE_TRANSFER_VERSION)
|
||||||
|
def create(self, share_id, name=None):
|
||||||
|
"""Creates a share transfer.
|
||||||
|
|
||||||
|
:param share_id: The ID of the share to transfer.
|
||||||
|
:param name: The name of the transfer.
|
||||||
|
:rtype: :class:`ShareTransfer`
|
||||||
|
"""
|
||||||
|
body = {'transfer': {'share_id': share_id,
|
||||||
|
'name': name}}
|
||||||
|
|
||||||
|
return self._create('/share-transfers', body, 'transfer')
|
||||||
|
|
||||||
|
@api_versions.wraps(constants.SHARE_TRANSFER_VERSION)
|
||||||
|
def accept(self, transfer, auth_key, clear_access_rules=False):
|
||||||
|
"""Accept a share transfer.
|
||||||
|
|
||||||
|
:param transfer_id: The ID of the transfer to accept.
|
||||||
|
:param auth_key: The auth_key of the transfer.
|
||||||
|
:param clear_access_rules: Transfer share without access rules
|
||||||
|
:rtype: :class:`ShareTransfer`
|
||||||
|
"""
|
||||||
|
transfer_id = base.getid(transfer)
|
||||||
|
body = {'accept': {'auth_key': auth_key,
|
||||||
|
'clear_access_rules': clear_access_rules}}
|
||||||
|
|
||||||
|
self._accept('/share-transfers/%s/accept' % transfer_id, body)
|
||||||
|
|
||||||
|
@api_versions.wraps(constants.SHARE_TRANSFER_VERSION)
|
||||||
|
def get(self, transfer_id):
|
||||||
|
"""Show details of a share transfer.
|
||||||
|
|
||||||
|
:param transfer_id: The ID of the share transfer to display.
|
||||||
|
:rtype: :class:`ShareTransfer`
|
||||||
|
"""
|
||||||
|
return self._get("/share-transfers/%s" % transfer_id, "transfer")
|
||||||
|
|
||||||
|
@api_versions.wraps(constants.SHARE_TRANSFER_VERSION)
|
||||||
|
def list(self, detailed=True, search_opts=None,
|
||||||
|
sort_key=None, sort_dir=None):
|
||||||
|
"""Get a list of all share transfer.
|
||||||
|
|
||||||
|
:param detailed: Get detailed object information.
|
||||||
|
:param search_opts: Filtering options.
|
||||||
|
:param sort_key: Key to be sorted (i.e. 'created_at').
|
||||||
|
:param sort_dir: Sort direction, should be 'desc' or 'asc'.
|
||||||
|
:rtype: list of :class:`ShareTransfer`
|
||||||
|
"""
|
||||||
|
if search_opts is None:
|
||||||
|
search_opts = {}
|
||||||
|
|
||||||
|
if sort_key is not None:
|
||||||
|
if sort_key in constants.SHARE_TRANSFER_SORT_KEY_VALUES:
|
||||||
|
search_opts['sort_key'] = sort_key
|
||||||
|
# NOTE: Replace aliases with appropriate keys
|
||||||
|
if sort_key == 'name':
|
||||||
|
search_opts['sort_key'] = 'display_name'
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
'sort_key must be one of the following: %s.'
|
||||||
|
% ', '.join(constants.SHARE_TRANSFER_SORT_KEY_VALUES))
|
||||||
|
|
||||||
|
if sort_dir is not None:
|
||||||
|
if sort_dir in constants.SORT_DIR_VALUES:
|
||||||
|
search_opts['sort_dir'] = sort_dir
|
||||||
|
else:
|
||||||
|
raise ValueError('sort_dir must be one of the following: %s.'
|
||||||
|
% ', '.join(constants.SORT_DIR_VALUES))
|
||||||
|
|
||||||
|
query_string = self._build_query_string(search_opts)
|
||||||
|
|
||||||
|
if detailed:
|
||||||
|
path = "/share-transfers/detail%s" % (query_string,)
|
||||||
|
else:
|
||||||
|
path = "/share-transfers%s" % (query_string,)
|
||||||
|
|
||||||
|
return self._list(path, 'transfers')
|
||||||
|
|
||||||
|
@api_versions.wraps(constants.SHARE_TRANSFER_VERSION)
|
||||||
|
def delete(self, transfer_id):
|
||||||
|
"""Delete a share transfer.
|
||||||
|
|
||||||
|
:param transfer_id: The :class:`ShareTransfer` to delete.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._delete("/share-transfers/%s" % base.getid(transfer_id))
|
@ -125,6 +125,11 @@ def _find_share(cs, share):
|
|||||||
return apiclient_utils.find_resource(cs.shares, share)
|
return apiclient_utils.find_resource(cs.shares, share)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_share_transfer(cs, transfer):
|
||||||
|
"""Get a share transfer by ID."""
|
||||||
|
return apiclient_utils.find_resource(cs.transfers, transfer)
|
||||||
|
|
||||||
|
|
||||||
@api_versions.wraps("1.0", "2.8")
|
@api_versions.wraps("1.0", "2.8")
|
||||||
def _print_share(cs, share):
|
def _print_share(cs, share):
|
||||||
info = share._info.copy()
|
info = share._info.copy()
|
||||||
@ -6532,6 +6537,212 @@ def do_share_replica_resync(cs, args):
|
|||||||
cs.share_replicas.resync(replica)
|
cs.share_replicas.resync(replica)
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Share Transfer
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def _print_share_transfer(transfer):
|
||||||
|
info = transfer._info.copy()
|
||||||
|
info.pop('links', None)
|
||||||
|
|
||||||
|
cliutils.print_dict(info)
|
||||||
|
|
||||||
|
|
||||||
|
@api_versions.wraps("2.77")
|
||||||
|
@cliutils.arg(
|
||||||
|
'share',
|
||||||
|
metavar='<share>',
|
||||||
|
help='Name or ID of share to transfer.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--name',
|
||||||
|
metavar='<name>',
|
||||||
|
default=None,
|
||||||
|
help='Transfer name. Default=None.')
|
||||||
|
def do_share_transfer_create(cs, args):
|
||||||
|
"""Creates a share transfer."""
|
||||||
|
share = _find_share(cs, args.share)
|
||||||
|
transfer = cs.transfers.create(share.id,
|
||||||
|
args.name)
|
||||||
|
_print_share_transfer(transfer)
|
||||||
|
|
||||||
|
|
||||||
|
@api_versions.wraps("2.77")
|
||||||
|
@cliutils.arg(
|
||||||
|
'transfer',
|
||||||
|
metavar='<transfer>',
|
||||||
|
nargs='+',
|
||||||
|
help='ID or name of the transfer(s).')
|
||||||
|
def do_share_transfer_delete(cs, args):
|
||||||
|
"""Remove one or more transfers."""
|
||||||
|
failure_count = 0
|
||||||
|
|
||||||
|
for transfer in args.transfer:
|
||||||
|
try:
|
||||||
|
transfer_ref = _find_share_transfer(cs, transfer)
|
||||||
|
transfer_ref.delete()
|
||||||
|
except Exception as e:
|
||||||
|
failure_count += 1
|
||||||
|
print("Delete for share transfer %s failed: %s" % (transfer, e),
|
||||||
|
file=sys.stderr)
|
||||||
|
|
||||||
|
if failure_count == len(args.transfer):
|
||||||
|
raise exceptions.CommandError("Unable to delete any of the specified "
|
||||||
|
"transfers.")
|
||||||
|
|
||||||
|
|
||||||
|
@api_versions.wraps("2.77")
|
||||||
|
@cliutils.arg(
|
||||||
|
'transfer',
|
||||||
|
metavar='<transfer>',
|
||||||
|
help='ID of transfer to accept.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'auth_key',
|
||||||
|
metavar='<auth_key>',
|
||||||
|
help='Authentication key of transfer to accept.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--clear-rules',
|
||||||
|
'--clear_rules',
|
||||||
|
dest='clear_rules',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help="Whether manila should clean up the access rules after the "
|
||||||
|
"transfer is complete. (Default=False)")
|
||||||
|
def do_share_transfer_accept(cs, args):
|
||||||
|
"""Accepts a share transfer."""
|
||||||
|
cs.transfers.accept(args.transfer, args.auth_key,
|
||||||
|
clear_access_rules=args.clear_rules)
|
||||||
|
|
||||||
|
|
||||||
|
@api_versions.wraps("2.77")
|
||||||
|
@cliutils.arg(
|
||||||
|
'--all-tenants', '--all-projects',
|
||||||
|
action='single_alias',
|
||||||
|
dest='all_projects',
|
||||||
|
metavar='<0|1>',
|
||||||
|
nargs='?',
|
||||||
|
type=int,
|
||||||
|
const=1,
|
||||||
|
default=0,
|
||||||
|
help='Shows details for all tenants. (Admin only).')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--name',
|
||||||
|
metavar='<name>',
|
||||||
|
default=None,
|
||||||
|
action='single_alias',
|
||||||
|
help='Transfer name. Default=None.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--id',
|
||||||
|
metavar='<id>',
|
||||||
|
default=None,
|
||||||
|
action='single_alias',
|
||||||
|
help='Transfer ID. Default=None.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--resource-type', '--resource_type',
|
||||||
|
metavar='<resource_type>',
|
||||||
|
default=None,
|
||||||
|
action='single_alias',
|
||||||
|
help='Transfer type, which can be share or network. Default=None.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--resource-id', '--resource_id',
|
||||||
|
metavar='<resource_id>',
|
||||||
|
default=None,
|
||||||
|
action='single_alias',
|
||||||
|
help='Transfer resource id. Default=None.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--source-project-id', '--source_project_id',
|
||||||
|
metavar='<source_project_id>',
|
||||||
|
default=None,
|
||||||
|
action='single_alias',
|
||||||
|
help='Transfer source project id. Default=None.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--limit',
|
||||||
|
metavar='<limit>',
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help='Maximum number of messages to return. (Default=None)')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--offset',
|
||||||
|
metavar="<offset>",
|
||||||
|
default=None,
|
||||||
|
help='Start position of message listing.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--sort-key', '--sort_key',
|
||||||
|
metavar='<sort_key>',
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
action='single_alias',
|
||||||
|
help='Key to be sorted, available keys are %(keys)s. Default=None.'
|
||||||
|
% {'keys': constants.SHARE_TRANSFER_SORT_KEY_VALUES})
|
||||||
|
@cliutils.arg(
|
||||||
|
'--sort-dir', '--sort_dir',
|
||||||
|
metavar='<sort_dir>',
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
action='single_alias',
|
||||||
|
help='Sort direction, available values are %(values)s. '
|
||||||
|
'Optional: Default=None.' % {'values': constants.SORT_DIR_VALUES})
|
||||||
|
@cliutils.arg(
|
||||||
|
'--detailed',
|
||||||
|
dest='detailed',
|
||||||
|
metavar='<0|1>',
|
||||||
|
nargs='?',
|
||||||
|
type=int,
|
||||||
|
const=1,
|
||||||
|
default=0,
|
||||||
|
help="Show detailed information about filtered share transfers.")
|
||||||
|
@cliutils.arg(
|
||||||
|
'--columns',
|
||||||
|
metavar='<columns>',
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help='Comma separated list of columns to be displayed '
|
||||||
|
'example --columns "id,resource_id".')
|
||||||
|
def do_share_transfer_list(cs, args):
|
||||||
|
"""Lists all transfers."""
|
||||||
|
if args.columns is not None:
|
||||||
|
list_of_keys = _split_columns(columns=args.columns)
|
||||||
|
else:
|
||||||
|
list_of_keys = ['ID', 'Name', 'Resource Type', 'Resource Id']
|
||||||
|
|
||||||
|
if args.detailed:
|
||||||
|
list_of_keys.extend(['Created At', 'Expires At', 'Source Project Id',
|
||||||
|
'Destination Project Id', 'Accepted'])
|
||||||
|
|
||||||
|
all_projects = int(
|
||||||
|
os.environ.get("ALL_TENANTS",
|
||||||
|
os.environ.get("ALL_PROJECTS",
|
||||||
|
args.all_projects))
|
||||||
|
)
|
||||||
|
|
||||||
|
search_opts = {
|
||||||
|
'offset': args.offset,
|
||||||
|
'limit': args.limit,
|
||||||
|
'all_tenants': all_projects,
|
||||||
|
'id': args.id,
|
||||||
|
'name': args.name,
|
||||||
|
'resource_type': args.resource_type,
|
||||||
|
'resource_id': args.resource_id,
|
||||||
|
'source_project_id': args.source_project_id,
|
||||||
|
}
|
||||||
|
share_transfers = cs.transfers.list(
|
||||||
|
detailed=args.detailed, search_opts=search_opts,
|
||||||
|
sort_key=args.sort_key, sort_dir=args.sort_dir)
|
||||||
|
cliutils.print_list(share_transfers, fields=list_of_keys,
|
||||||
|
sortby_index=None)
|
||||||
|
|
||||||
|
|
||||||
|
@api_versions.wraps("2.77")
|
||||||
|
@cliutils.arg(
|
||||||
|
'transfer',
|
||||||
|
metavar='<transfer>',
|
||||||
|
help='Name or ID of transfer to show.')
|
||||||
|
def do_share_transfer_show(cs, args):
|
||||||
|
"""Delete a transfer."""
|
||||||
|
transfer = _find_share_transfer(cs, args.transfer)
|
||||||
|
_print_share_transfer(transfer)
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# User Messages
|
# User Messages
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Support transferring shares between projects starting from API version ``2.77``.
|
@ -159,6 +159,11 @@ openstack.share.v2 =
|
|||||||
share_server_migration_complete = manilaclient.osc.v2.share_servers:ShareServerMigrationComplete
|
share_server_migration_complete = manilaclient.osc.v2.share_servers:ShareServerMigrationComplete
|
||||||
share_server_migration_show = manilaclient.osc.v2.share_servers:ShareServerMigrationShow
|
share_server_migration_show = manilaclient.osc.v2.share_servers:ShareServerMigrationShow
|
||||||
share_server_migration_start = manilaclient.osc.v2.share_servers:ShareServerMigrationStart
|
share_server_migration_start = manilaclient.osc.v2.share_servers:ShareServerMigrationStart
|
||||||
|
share_transfer_create = manilaclient.osc.v2.share_transfers:CreateShareTransfer
|
||||||
|
share_transfer_delete = manilaclient.osc.v2.share_transfers:DeleteShareTransfer
|
||||||
|
share_transfer_list = manilaclient.osc.v2.share_transfers:ListShareTransfer
|
||||||
|
share_transfer_show = manilaclient.osc.v2.share_transfers:ShowShareTransfer
|
||||||
|
share_transfer_accept = manilaclient.osc.v2.share_transfers:AcceptShareTransfer
|
||||||
|
|
||||||
[coverage:run]
|
[coverage:run]
|
||||||
omit = manilaclient/tests/*
|
omit = manilaclient/tests/*
|
||||||
|
Loading…
Reference in New Issue
Block a user