[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:
Maari Tamm 2020-11-14 19:06:05 +00:00
parent 07898faca6
commit bf3e7cb716
4 changed files with 440 additions and 0 deletions

View File

@ -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
================== ==================

View File

@ -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)

View File

@ -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
)

View File

@ -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