[OSC] Implement Share Adopt & Abandon Commands
This commit adds 'openstack share adopt' and 'openstack share abandon' commands, that implement the same functionality as 'manila manage' and 'manila unmanage' commands Usage: openstack share adopt <service-host> <protocol> <export-path> openstack share abandon <share> Partially-implements bp openstack-client-support Change-Id: I39919d38854387af21da410849905698ad261e9f
This commit is contained in:
parent
07898faca6
commit
bf3e7cb716
|
@ -29,6 +29,12 @@ shares
|
|||
.. autoprogram-cliff:: openstack.share.v2
|
||||
:command: share resize
|
||||
|
||||
.. autoprogram-cliff:: openstack.share.v2
|
||||
:command: share adopt
|
||||
|
||||
.. autoprogram-cliff:: openstack.share.v2
|
||||
:command: share abandon
|
||||
|
||||
==================
|
||||
share access rules
|
||||
==================
|
||||
|
|
|
@ -21,6 +21,7 @@ from osc_lib.command import command
|
|||
from osc_lib import exceptions
|
||||
from osc_lib import utils as oscutils
|
||||
|
||||
from manilaclient import api_versions
|
||||
from manilaclient.common._i18n import _
|
||||
from manilaclient.common.apiclient import utils as apiutils
|
||||
from manilaclient.common import cliutils
|
||||
|
@ -777,3 +778,186 @@ class ResizeShare(command.Command):
|
|||
):
|
||||
raise exceptions.CommandError(_(
|
||||
"Share not available after resize attempt."))
|
||||
|
||||
|
||||
class AdoptShare(command.ShowOne):
|
||||
"""Adopt share not handled by Manila (Admin only)."""
|
||||
_description = _("Adopt a share")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(AdoptShare, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'service_host',
|
||||
metavar="<service-host>",
|
||||
help=_('Service host: some.host@driver#pool.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'protocol',
|
||||
metavar="<protocol>",
|
||||
help=_(
|
||||
'Protocol of the share to manage, such as NFS or CIFS.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'export_path',
|
||||
metavar="<export-path>",
|
||||
help=_('Share export path, NFS share such as: '
|
||||
'10.0.0.1:/example_path, CIFS share such as: '
|
||||
'\\\\10.0.0.1\\example_cifs_share.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--name',
|
||||
metavar="<name>",
|
||||
default=None,
|
||||
help=_('Optional share name. (Default=None)')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--description',
|
||||
metavar="<description>",
|
||||
default=None,
|
||||
help=_('Optional share description. (Default=None)')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--share-type',
|
||||
metavar="<share-type>",
|
||||
default=None,
|
||||
help=_(
|
||||
'Optional share type assigned to share. (Default=None)')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--driver-options',
|
||||
type=str,
|
||||
nargs='*',
|
||||
metavar='<key=value>',
|
||||
default=None,
|
||||
help=_(
|
||||
'Optional driver options as key=value pairs (Default=None).')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--public',
|
||||
action='store_true',
|
||||
help=_('Level of visibility for share. Defines whether other '
|
||||
'projects are able to see it or not. Available only for '
|
||||
'microversion >= 2.8. (Default=False)')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--share-server-id',
|
||||
metavar="<share-server-id>",
|
||||
help=_('Share server associated with share when using a share '
|
||||
'type with "driver_handles_share_servers" extra_spec '
|
||||
'set to True. Available only for microversion >= 2.49. '
|
||||
'(Default=None)')
|
||||
)
|
||||
parser.add_argument(
|
||||
"--wait",
|
||||
action='store_true',
|
||||
help=_("Wait until share is adopted")
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
share_client = self.app.client_manager.share
|
||||
|
||||
kwargs = {
|
||||
'service_host': parsed_args.service_host,
|
||||
'protocol': parsed_args.protocol,
|
||||
'export_path': parsed_args.export_path,
|
||||
'name': parsed_args.name,
|
||||
'description': parsed_args.description
|
||||
}
|
||||
|
||||
share_type = None
|
||||
if parsed_args.share_type:
|
||||
share_type = apiutils.find_resource(share_client.share_types,
|
||||
parsed_args.share_type).id
|
||||
kwargs['share_type'] = share_type
|
||||
|
||||
driver_options = None
|
||||
if parsed_args.driver_options:
|
||||
driver_options = utils.extract_properties(
|
||||
parsed_args.driver_options)
|
||||
kwargs['driver_options'] = driver_options
|
||||
|
||||
if parsed_args.public:
|
||||
if share_client.api_version >= api_versions.APIVersion("2.8"):
|
||||
kwargs['public'] = True
|
||||
else:
|
||||
raise exceptions.CommandError(
|
||||
'Setting share visibility while adopting a share is '
|
||||
'available only for API microversion >= 2.8')
|
||||
|
||||
if parsed_args.share_server_id:
|
||||
if share_client.api_version >= api_versions.APIVersion("2.49"):
|
||||
kwargs['share_server_id'] = parsed_args.share_server_id
|
||||
else:
|
||||
raise exceptions.CommandError(
|
||||
'Selecting a share server ID is available only for '
|
||||
'API microversion >= 2.49')
|
||||
|
||||
share = share_client.shares.manage(**kwargs)
|
||||
|
||||
if parsed_args.wait:
|
||||
if not oscutils.wait_for_status(
|
||||
status_f=share_client.shares.get,
|
||||
res_id=share.id,
|
||||
success_status=['available'],
|
||||
error_status=['manage_error', 'error']
|
||||
):
|
||||
LOG.error(_("ERROR: Share is in error state."))
|
||||
|
||||
share = apiutils.find_resource(share_client.shares,
|
||||
share.id)
|
||||
share._info.pop('links', None)
|
||||
|
||||
return self.dict2columns(share._info)
|
||||
|
||||
|
||||
class AbandonShare(command.Command):
|
||||
"""Abandon a share (Admin only)."""
|
||||
_description = _("Abandon a share")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(AbandonShare, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'share',
|
||||
metavar="<share>",
|
||||
nargs="+",
|
||||
help=_('Name or ID of the share(s)')
|
||||
)
|
||||
parser.add_argument(
|
||||
"--wait",
|
||||
action='store_true',
|
||||
help=_("Wait until share is abandoned")
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
share_client = self.app.client_manager.share
|
||||
result = 0
|
||||
|
||||
for share in parsed_args.share:
|
||||
try:
|
||||
share_obj = apiutils.find_resource(
|
||||
share_client.shares, share
|
||||
)
|
||||
share_client.shares.unmanage(share_obj)
|
||||
|
||||
if parsed_args.wait:
|
||||
# 'wait_for_delete' checks that the resource is no longer
|
||||
# retrievable with the given 'res_id' so we can use it
|
||||
# to check that the share has been abandoned
|
||||
if not oscutils.wait_for_delete(
|
||||
manager=share_client.shares,
|
||||
res_id=share_obj.id):
|
||||
result += 1
|
||||
|
||||
except Exception as e:
|
||||
result += 1
|
||||
LOG.error(_("Failed to abandon share with "
|
||||
"name or ID '%(share)s': %(e)s"),
|
||||
{'share': share, 'e': e})
|
||||
|
||||
if result > 0:
|
||||
total = len(parsed_args.share)
|
||||
msg = (_("Failed to abandon %(result)s out of %(total)s shares.")
|
||||
% {'result': result, 'total': total})
|
||||
raise exceptions.CommandError(msg)
|
||||
|
|
|
@ -20,6 +20,8 @@ import uuid
|
|||
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
|
||||
from osc_lib import exceptions as osc_exceptions
|
||||
|
||||
from manilaclient import api_versions
|
||||
from manilaclient.api_versions import MAX_VERSION
|
||||
from manilaclient.common.apiclient import exceptions
|
||||
from manilaclient.common import cliutils
|
||||
from manilaclient.osc.v2 import share as osc_shares
|
||||
|
@ -44,6 +46,12 @@ class TestShare(manila_fakes.TestShare):
|
|||
self.snapshots_mock = self.app.client_manager.share.share_snapshots
|
||||
self.snapshots_mock.reset_mock()
|
||||
|
||||
self.share_types_mock = self.app.client_manager.share.share_types
|
||||
self.share_types_mock.reset_mock()
|
||||
|
||||
self.app.client_manager.share.api_version = api_versions.APIVersion(
|
||||
MAX_VERSION)
|
||||
|
||||
def setup_shares_mock(self, count):
|
||||
shares = manila_fakes.FakeShare.create_shares(count=count)
|
||||
|
||||
|
@ -1409,3 +1417,243 @@ class TestResizeShare(TestShare):
|
|||
self.cmd.take_action,
|
||||
parsed_args
|
||||
)
|
||||
|
||||
|
||||
class TestAdoptShare(TestShare):
|
||||
|
||||
def setUp(self):
|
||||
super(TestAdoptShare, self).setUp()
|
||||
|
||||
self._share_type = manila_fakes.FakeShareType.create_one_sharetype()
|
||||
self.share_types_mock.get.return_value = self._share_type
|
||||
|
||||
self._share = manila_fakes.FakeShare.create_one_share(
|
||||
attrs={
|
||||
'status': 'available',
|
||||
'share_type': self._share_type.id,
|
||||
'share_server_id': 'server-id' + uuid.uuid4().hex})
|
||||
self.shares_mock.get.return_value = self._share
|
||||
|
||||
self.shares_mock.manage.return_value = self._share
|
||||
|
||||
# Get the command objects to test
|
||||
self.cmd = osc_shares.AdoptShare(self.app, None)
|
||||
|
||||
self.datalist = tuple(self._share._info.values())
|
||||
self.columns = tuple(self._share._info.keys())
|
||||
|
||||
def test_share_adopt_missing_args(self):
|
||||
arglist = []
|
||||
verifylist = []
|
||||
|
||||
self.assertRaises(osc_utils.ParserException,
|
||||
self.check_parser, self.cmd, arglist, verifylist)
|
||||
|
||||
def test_share_adopt_required_args(self):
|
||||
arglist = [
|
||||
'some.host@driver#pool',
|
||||
'NFS',
|
||||
'10.0.0.1:/example_path'
|
||||
]
|
||||
verifylist = [
|
||||
('service_host', 'some.host@driver#pool'),
|
||||
('protocol', 'NFS'),
|
||||
('export_path', '10.0.0.1:/example_path')
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.shares_mock.manage.assert_called_with(
|
||||
description=None,
|
||||
export_path='10.0.0.1:/example_path',
|
||||
name=None,
|
||||
protocol='NFS',
|
||||
service_host='some.host@driver#pool'
|
||||
)
|
||||
self.assertCountEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
|
||||
def test_share_adopt(self):
|
||||
arglist = [
|
||||
'some.host@driver#pool',
|
||||
'NFS',
|
||||
'10.0.0.1:/example_path',
|
||||
'--name', self._share.id,
|
||||
'--description', self._share.description,
|
||||
'--share-type', self._share.share_type,
|
||||
'--driver-options', 'key1=value1', 'key2=value2',
|
||||
'--wait',
|
||||
'--public',
|
||||
'--share-server-id', self._share.share_server_id
|
||||
|
||||
]
|
||||
verifylist = [
|
||||
('service_host', 'some.host@driver#pool'),
|
||||
('protocol', 'NFS'),
|
||||
('export_path', '10.0.0.1:/example_path'),
|
||||
('name', self._share.id),
|
||||
('description', self._share.description),
|
||||
('share_type', self._share_type.id),
|
||||
('driver_options', ['key1=value1', 'key2=value2']),
|
||||
('wait', True),
|
||||
('public', True),
|
||||
('share_server_id', self._share.share_server_id)
|
||||
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.shares_mock.manage.assert_called_with(
|
||||
description=self._share.description,
|
||||
driver_options={'key1': 'value1', 'key2': 'value2'},
|
||||
export_path='10.0.0.1:/example_path',
|
||||
name=self._share.id,
|
||||
protocol='NFS',
|
||||
service_host='some.host@driver#pool',
|
||||
share_server_id=self._share.share_server_id,
|
||||
share_type=self._share_type.id,
|
||||
public=True
|
||||
)
|
||||
self.assertCountEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
|
||||
@mock.patch('manilaclient.osc.v2.share.LOG')
|
||||
def test_share_adopt_wait_error(self, mock_logger):
|
||||
arglist = [
|
||||
'some.host@driver#pool',
|
||||
'NFS',
|
||||
'10.0.0.1:/example_path',
|
||||
'--wait'
|
||||
]
|
||||
verifylist = [
|
||||
('service_host', 'some.host@driver#pool'),
|
||||
('protocol', 'NFS'),
|
||||
('export_path', '10.0.0.1:/example_path'),
|
||||
('wait', True)
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
with mock.patch('osc_lib.utils.wait_for_status', return_value=False):
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.shares_mock.manage.assert_called_with(
|
||||
description=None,
|
||||
export_path='10.0.0.1:/example_path',
|
||||
name=None,
|
||||
protocol='NFS',
|
||||
service_host='some.host@driver#pool'
|
||||
)
|
||||
|
||||
mock_logger.error.assert_called_with(
|
||||
"ERROR: Share is in error state.")
|
||||
|
||||
self.shares_mock.get.assert_called_with(self._share.id)
|
||||
self.assertCountEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
|
||||
def test_share_adopt_visibility_api_version_exception(self):
|
||||
self.app.client_manager.share.api_version = api_versions.APIVersion(
|
||||
"2.7")
|
||||
|
||||
arglist = [
|
||||
'some.host@driver#pool',
|
||||
'NFS',
|
||||
'10.0.0.1:/example_path',
|
||||
'--public'
|
||||
|
||||
]
|
||||
verifylist = [
|
||||
('service_host', 'some.host@driver#pool'),
|
||||
('protocol', 'NFS'),
|
||||
('export_path', '10.0.0.1:/example_path'),
|
||||
('public', True)
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self.assertRaises(
|
||||
osc_exceptions.CommandError, self.cmd.take_action, parsed_args)
|
||||
|
||||
def test_share_adopt_share_server_api_version_exception(self):
|
||||
self.app.client_manager.share.api_version = api_versions.APIVersion(
|
||||
"2.48")
|
||||
|
||||
arglist = [
|
||||
'some.host@driver#pool',
|
||||
'NFS',
|
||||
'10.0.0.1:/example_path',
|
||||
'--share-server-id', self._share.share_server_id
|
||||
|
||||
]
|
||||
verifylist = [
|
||||
('service_host', 'some.host@driver#pool'),
|
||||
('protocol', 'NFS'),
|
||||
('export_path', '10.0.0.1:/example_path'),
|
||||
('share_server_id', self._share.share_server_id)
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self.assertRaises(
|
||||
osc_exceptions.CommandError, self.cmd.take_action, parsed_args)
|
||||
|
||||
|
||||
class TestAbandonShare(TestShare):
|
||||
|
||||
def setUp(self):
|
||||
super(TestAbandonShare, self).setUp()
|
||||
|
||||
self._share = manila_fakes.FakeShare.create_one_share(
|
||||
attrs={'status': 'available'})
|
||||
self.shares_mock.get.return_value = self._share
|
||||
|
||||
# Get the command objects to test
|
||||
self.cmd = osc_shares.AbandonShare(self.app, None)
|
||||
|
||||
def test_share_abandon(self):
|
||||
arglist = [
|
||||
self._share.id,
|
||||
]
|
||||
verifylist = [
|
||||
('share', [self._share.id]),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.shares_mock.unmanage.assert_called_with(self._share)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_share_abandon_wait(self):
|
||||
arglist = [
|
||||
self._share.id,
|
||||
'--wait'
|
||||
]
|
||||
verifylist = [
|
||||
('share', [self._share.id]),
|
||||
('wait', True)
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
with mock.patch('osc_lib.utils.wait_for_delete', return_value=True):
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.shares_mock.unmanage.assert_called_with(self._share)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_share_abandon_wait_error(self):
|
||||
arglist = [
|
||||
self._share.id,
|
||||
'--wait'
|
||||
]
|
||||
verifylist = [
|
||||
('share', [self._share.id]),
|
||||
('wait', True)
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
with mock.patch('osc_lib.utils.wait_for_delete', return_value=False):
|
||||
self.assertRaises(
|
||||
osc_exceptions.CommandError,
|
||||
self.cmd.take_action,
|
||||
parsed_args
|
||||
)
|
||||
|
|
|
@ -42,6 +42,8 @@ openstack.share.v2 =
|
|||
share_set = manilaclient.osc.v2.share:SetShare
|
||||
share_unset = manilaclient.osc.v2.share:UnsetShare
|
||||
share_resize = manilaclient.osc.v2.share:ResizeShare
|
||||
share_adopt = manilaclient.osc.v2.share:AdoptShare
|
||||
share_abandon = manilaclient.osc.v2.share:AbandonShare
|
||||
share_access_create = manilaclient.osc.v2.share_access_rules:ShareAccessAllow
|
||||
share_access_delete = manilaclient.osc.v2.share_access_rules:ShareAccessDeny
|
||||
share_access_list = manilaclient.osc.v2.share_access_rules:ListShareAccess
|
||||
|
|
Loading…
Reference in New Issue