[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
|
.. autoprogram-cliff:: openstack.share.v2
|
||||||
:command: share resize
|
:command: share resize
|
||||||
|
|
||||||
|
.. autoprogram-cliff:: openstack.share.v2
|
||||||
|
:command: share adopt
|
||||||
|
|
||||||
|
.. autoprogram-cliff:: openstack.share.v2
|
||||||
|
:command: share abandon
|
||||||
|
|
||||||
==================
|
==================
|
||||||
share access rules
|
share access rules
|
||||||
==================
|
==================
|
||||||
|
|
|
@ -21,6 +21,7 @@ from osc_lib.command import command
|
||||||
from osc_lib import exceptions
|
from osc_lib import exceptions
|
||||||
from osc_lib import utils as oscutils
|
from osc_lib import utils as oscutils
|
||||||
|
|
||||||
|
from manilaclient import api_versions
|
||||||
from manilaclient.common._i18n import _
|
from manilaclient.common._i18n import _
|
||||||
from manilaclient.common.apiclient import utils as apiutils
|
from manilaclient.common.apiclient import utils as apiutils
|
||||||
from manilaclient.common import cliutils
|
from manilaclient.common import cliutils
|
||||||
|
@ -777,3 +778,186 @@ class ResizeShare(command.Command):
|
||||||
):
|
):
|
||||||
raise exceptions.CommandError(_(
|
raise exceptions.CommandError(_(
|
||||||
"Share not available after resize attempt."))
|
"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 openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
|
||||||
from osc_lib import exceptions as osc_exceptions
|
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.apiclient import exceptions
|
||||||
from manilaclient.common import cliutils
|
from manilaclient.common import cliutils
|
||||||
from manilaclient.osc.v2 import share as osc_shares
|
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 = self.app.client_manager.share.share_snapshots
|
||||||
self.snapshots_mock.reset_mock()
|
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):
|
def setup_shares_mock(self, count):
|
||||||
shares = manila_fakes.FakeShare.create_shares(count=count)
|
shares = manila_fakes.FakeShare.create_shares(count=count)
|
||||||
|
|
||||||
|
@ -1409,3 +1417,243 @@ class TestResizeShare(TestShare):
|
||||||
self.cmd.take_action,
|
self.cmd.take_action,
|
||||||
parsed_args
|
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_set = manilaclient.osc.v2.share:SetShare
|
||||||
share_unset = manilaclient.osc.v2.share:UnsetShare
|
share_unset = manilaclient.osc.v2.share:UnsetShare
|
||||||
share_resize = manilaclient.osc.v2.share:ResizeShare
|
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_create = manilaclient.osc.v2.share_access_rules:ShareAccessAllow
|
||||||
share_access_delete = manilaclient.osc.v2.share_access_rules:ShareAccessDeny
|
share_access_delete = manilaclient.osc.v2.share_access_rules:ShareAccessDeny
|
||||||
share_access_list = manilaclient.osc.v2.share_access_rules:ListShareAccess
|
share_access_list = manilaclient.osc.v2.share_access_rules:ListShareAccess
|
||||||
|
|
Loading…
Reference in New Issue