Add CLI commands for Manage-Unmanage of Share Servers

- Added CLI commands for managing and unmanaging share servers.
- Updated CLI command for managing shares to accept
  share_server_id parameter.

API microversion has been bumped to 2.49.

Partially-implements: bp manage-unmanage-with-share-servers
Change-Id: If1403079b20471645bf869da74bf4db37d59811c
This commit is contained in:
Rodrigo Barbieri 2018-10-27 09:18:21 -03:00 committed by Tom Barron
parent 7d6ef621c3
commit 07564879ae
16 changed files with 558 additions and 47 deletions

View File

@ -65,6 +65,10 @@ iniset $MANILACLIENT_CONF DEFAULT access_types_mapping "nfs:ip,cifs:user"
# Dummy driver is capable of running share migration tests # Dummy driver is capable of running share migration tests
iniset $MANILACLIENT_CONF DEFAULT run_migration_tests "True" iniset $MANILACLIENT_CONF DEFAULT run_migration_tests "True"
# Dummy driver is capable of running share manage tests
iniset $MANILACLIENT_CONF DEFAULT run_manage_tests "True"
# Running mountable snapshot tests in dummy driver # Running mountable snapshot tests in dummy driver
iniset $MANILACLIENT_CONF DEFAULT run_mount_snapshot_tests "True" iniset $MANILACLIENT_CONF DEFAULT run_mount_snapshot_tests "True"

View File

@ -27,7 +27,7 @@ from manilaclient import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
MAX_VERSION = '2.47' MAX_VERSION = '2.49'
MIN_VERSION = '2.0' MIN_VERSION = '2.0'
DEPRECATED_VERSION = '1.0' DEPRECATED_VERSION = '1.0'
_VERSIONED_METHOD_MAP = {} _VERSIONED_METHOD_MAP = {}

View File

@ -86,3 +86,11 @@ MESSAGE_SORT_KEY_VALUES = (
'detail_id', 'resource_id', 'message_level', 'expires_at', 'detail_id', 'resource_id', 'message_level', 'expires_at',
'request_id', 'created_at' 'request_id', 'created_at'
) )
STATUS_AVAILABLE = 'available'
STATUS_ERROR = 'error'
STATUS_ACTIVE = 'active'
STATUS_MANAGE_ERROR = 'manage_error'
STATUS_UNMANAGE_ERROR = 'unmanage_error'
STATUS_DELETING = 'deleting'
STATUS_CREATING = 'creating'

View File

@ -178,6 +178,12 @@ share_opts = [
help="Defines whether to run mountable snapshots tests or " help="Defines whether to run mountable snapshots tests or "
"not. Disable this feature if used driver doesn't " "not. Disable this feature if used driver doesn't "
"support it."), "support it."),
cfg.BoolOpt("run_manage_tests",
default=False,
help="Defines whether to run manage/unmanage tests or "
"not. Disable this feature if used driver does not "
"support it."),
] ]
# 2. Generate config # 2. Generate config

View File

@ -21,6 +21,7 @@ from tempest.lib.cli import base
from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions as lib_exc from tempest.lib import exceptions as lib_exc
from manilaclient.common import constants
from manilaclient import config from manilaclient import config
from manilaclient.tests.functional import client from manilaclient.tests.functional import client
from manilaclient.tests.functional import utils from manilaclient.tests.functional import utils
@ -272,7 +273,8 @@ class BaseTestCase(base.ClientTestBase):
else: else:
cls.method_resources.insert(0, resource) cls.method_resources.insert(0, resource)
if wait_for_creation: if wait_for_creation:
client.wait_for_share_status(share['id'], 'available') client.wait_for_resource_status(share['id'],
constants.STATUS_AVAILABLE)
return share return share
@classmethod @classmethod
@ -369,7 +371,7 @@ class BaseTestCase(base.ClientTestBase):
cleanup_in_class=cleanup_in_class, microversion=microversion, cleanup_in_class=cleanup_in_class, microversion=microversion,
wait_for_creation=False, client=client) wait_for_creation=False, client=client)
client.wait_for_share_status(share['id'], "error") client.wait_for_resource_status(share['id'], constants.STATUS_ERROR)
message = client.wait_for_message(share['id']) message = client.wait_for_message(share['id'])
resource = { resource = {

View File

@ -730,29 +730,30 @@ 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 wait_for_share_status(self, share, status, microversion=None): def wait_for_resource_status(self, resource_id, status, microversion=None,
resource_type="share"):
"""Waits for a share to reach a given status.""" """Waits for a share to reach a given status."""
body = self.get_share(share, microversion=microversion) get_func = getattr(self, 'get_' + resource_type)
share_name = body['name'] body = get_func(resource_id, microversion=microversion)
share_status = body['status'] share_status = body['status']
start = int(time.time()) start = int(time.time())
while share_status != status: while share_status != status:
time.sleep(self.build_interval) time.sleep(self.build_interval)
body = self.get_share(share, microversion=microversion) body = get_func(resource_id, microversion=microversion)
share_status = body['status'] share_status = body['status']
if share_status == status: if share_status == status:
return return
elif 'error' in share_status.lower(): elif 'error' in share_status.lower():
raise exceptions.ShareBuildErrorException(share=share) raise exceptions.ShareBuildErrorException(share=resource_id)
if int(time.time()) - start >= self.build_timeout: if int(time.time()) - start >= self.build_timeout:
message = ( message = ("Resource %(resource_id)s failed to reach "
"Share %(share_name)s failed to reach %(status)s status " "%(status)s status within the required time "
"within the required time (%(build_timeout)s s)." % { "(%(build_timeout)s)." %
"share_name": share_name, "status": status, {"resource_id": resource_id, "status": status,
"build_timeout": self.build_timeout}) "build_timeout": self.build_timeout})
raise tempest_lib_exc.TimeoutException(message) raise tempest_lib_exc.TimeoutException(message)
def wait_for_migration_task_state(self, share_id, dest_host, def wait_for_migration_task_state(self, share_id, dest_host,
@ -1441,6 +1442,32 @@ class ManilaCLIClient(base.CLIClient):
SHARE_SERVER, res_id=share_server, interval=3, timeout=60, SHARE_SERVER, res_id=share_server, interval=3, timeout=60,
microversion=microversion) microversion=microversion)
def unmanage_share(self, server_id):
return self.manila('unmanage %s ' % server_id)
def unmanage_server(self, share_server_id):
return self.manila('share-server-unmanage %s ' % share_server_id)
def share_server_manage(self, host, share_network, identifier,
driver_options=None):
if driver_options:
command = ('share-server-manage %s %s %s %s' %
(host, share_network, identifier, driver_options))
else:
command = ('share-server-manage %s %s %s' % (host, share_network,
identifier))
managed_share_server_raw = self.manila(command)
managed_share_server = output_parser.details(managed_share_server_raw)
return managed_share_server['id']
def manage_share(self, host, protocol, export_location, share_server):
managed_share_raw = self.manila(
'manage %s %s %s --share-server-id %s' % (host, protocol,
export_location,
share_server))
managed_share = output_parser.details(managed_share_raw)
return managed_share['id']
# user messages # user messages
def wait_for_message(self, resource_id): def wait_for_message(self, resource_id):

View File

@ -14,11 +14,15 @@
# under the License. # under the License.
import ddt import ddt
import testtools
from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions from tempest.lib import exceptions
from manilaclient.common import constants
from manilaclient import config from manilaclient import config
from manilaclient.tests.functional import base from manilaclient.tests.functional import base
from manilaclient.tests.functional import utils
CONF = config.CONF CONF = config.CONF
@ -79,12 +83,10 @@ class ShareServersReadWriteBase(base.BaseTestCase):
message = "Can run only with DHSS=True mode" message = "Can run only with DHSS=True mode"
raise cls.skipException(message) raise cls.skipException(message)
def test_get_and_delete_share_server(self): def _create_share_and_share_network(self):
name = data_utils.rand_name('autotest_share_name') name = data_utils.rand_name('autotest_share_name')
description = data_utils.rand_name('autotest_share_description') description = data_utils.rand_name('autotest_share_description')
# We create separate share network to be able to delete share server
# further knowing that it is not used by any other concurrent test.
common_share_network = self.client.get_share_network( common_share_network = self.client.get_share_network(
self.client.share_network) self.client.share_network)
neutron_net_id = ( neutron_net_id = (
@ -107,7 +109,22 @@ class ShareServersReadWriteBase(base.BaseTestCase):
description=description, description=description,
share_network=share_network['id'], share_network=share_network['id'],
client=self.client, client=self.client,
wait_for_creation=True
) )
self.share = self.client.get_share(self.share['id'])
return self.share, share_network
def _delete_share_and_share_server(self, share_id, share_server_id):
# Delete share
self.client.delete_share(share_id)
self.client.wait_for_share_deletion(share_id)
# Delete share server
self.client.delete_share_server(share_server_id)
self.client.wait_for_share_server_deletion(share_server_id)
def test_get_and_delete_share_server(self):
self.share, share_network = self._create_share_and_share_network()
share_server_id = self.client.get_share( share_server_id = self.client.get_share(
self.share['id'])['share_server_id'] self.share['id'])['share_server_id']
@ -117,17 +134,62 @@ class ShareServersReadWriteBase(base.BaseTestCase):
'id', 'host', 'status', 'created_at', 'updated_at', 'id', 'host', 'status', 'created_at', 'updated_at',
'share_network_id', 'share_network_name', 'project_id', 'share_network_id', 'share_network_name', 'project_id',
) )
if utils.is_microversion_supported('2.49'):
expected_keys += ('identifier', 'is_auto_deletable')
for key in expected_keys: for key in expected_keys:
self.assertIn(key, server) self.assertIn(key, server)
# Delete share self._delete_share_and_share_server(self.share['id'], share_server_id)
self.client.delete_share(self.share['id'])
self.client.wait_for_share_deletion(self.share['id'])
# Delete share server @testtools.skipUnless(
self.client.delete_share_server(share_server_id) CONF.run_manage_tests, 'Share Manage/Unmanage tests are disabled.')
@utils.skip_if_microversion_not_supported('2.49')
def test_manage_and_unmanage_share_server(self):
share, share_network = self._create_share_and_share_network()
share_server_id = self.client.get_share(
self.share['id'])['share_server_id']
server = self.client.get_share_server(share_server_id)
server_host = server['host']
export_location = self.client.list_share_export_locations(
self.share['id'])[0]['Path']
share_host = share['host']
identifier = server['identifier']
self.assertEqual('True', server['is_auto_deletable'])
# Unmanages share
self.client.unmanage_share(share['id'])
self.client.wait_for_share_deletion(share['id'])
server = self.client.get_share_server(share_server_id)
self.assertEqual('False', server['is_auto_deletable'])
# Unmanages share server
self.client.unmanage_server(share_server_id)
self.client.wait_for_share_server_deletion(share_server_id) self.client.wait_for_share_server_deletion(share_server_id)
# Manage share server
managed_share_server_id = self.client.share_server_manage(
server_host, share_network['id'], identifier)
self.client.wait_for_resource_status(
managed_share_server_id, constants.STATUS_ACTIVE,
resource_type='share_server')
managed_server = self.client.get_share_server(managed_share_server_id)
self.assertEqual('False', managed_server['is_auto_deletable'])
# Manage share
managed_share_id = self.client.manage_share(
share_host, self.protocol, export_location,
managed_share_server_id)
self.client.wait_for_resource_status(managed_share_id,
constants.STATUS_AVAILABLE)
self._delete_share_and_share_server(managed_share_id,
managed_share_server_id)
class ShareServersReadWriteNFSTest(ShareServersReadWriteBase): class ShareServersReadWriteNFSTest(ShareServersReadWriteBase):
protocol = 'nfs' protocol = 'nfs'

View File

@ -19,6 +19,7 @@ from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions from tempest.lib import exceptions
import testtools import testtools
from manilaclient.common import constants
from manilaclient import config from manilaclient import config
from manilaclient.tests.functional import base from manilaclient.tests.functional import base
@ -151,7 +152,8 @@ class SharesListReadWriteTest(base.BaseTestCase):
for share_id in (cls.private_share['id'], cls.public_share['id'], for share_id in (cls.private_share['id'], cls.public_share['id'],
cls.admin_private_share['id']): cls.admin_private_share['id']):
cls.get_admin_client().wait_for_share_status(share_id, 'available') cls.get_admin_client().wait_for_resource_status(
share_id, constants.STATUS_AVAILABLE)
def _list_shares(self, filters=None): def _list_shares(self, filters=None):
filters = filters or dict() filters = filters or dict()

View File

@ -179,16 +179,17 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
def get_share_servers_1234(self, **kw): def get_share_servers_1234(self, **kw):
share_servers = { share_servers = {
'share_servers': { 'share_server': {
'id': 1234, 'id': 1234,
'share_network_id': 'fake_network_id_1', 'share_network_id': 'fake_network_id_1',
'backend_details': {},
}, },
} }
return (200, {}, share_servers) return (200, {}, share_servers)
def get_share_servers_5678(self, **kw): def get_share_servers_5678(self, **kw):
share_servers = { share_servers = {
'share_servers': { 'share_server': {
'id': 5678, 'id': 5678,
'share_network_id': 'fake_network_id_2', 'share_network_id': 'fake_network_id_2',
}, },
@ -429,6 +430,36 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
post_shares_manage = post_os_share_manage post_shares_manage = post_os_share_manage
def post_share_servers_manage(self, body, **kw):
_body = {'share_server': {'id': 'fake'}}
resp = 202
if not ('host' in body['share_server']
and 'share_network' in body['share_server']
and 'identifier' in body['share_server']):
resp = 422
result = (resp, {}, _body)
return result
def post_share_servers_1234_action(self, body, **kw):
_body = None
assert len(list(body)) == 1
action = list(body)[0]
if action in ('reset_status', ):
assert 'status' in body.get(
'reset_status', body.get('os-reset_status'))
_body = {
'reset_status': {'status': body['reset_status']['status']}
}
elif action in ('unmanage', ):
assert 'force' in body[action]
resp = 202
result = (resp, {}, _body)
return result
def post_os_share_unmanage_1234_unmanage(self, **kw): def post_os_share_unmanage_1234_unmanage(self, **kw):
_body = None _body = None
resp = 202 resp = 202

View File

@ -13,8 +13,11 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import ddt
import mock import mock
from manilaclient.common.apiclient import base as common_base
from manilaclient.common import constants
from manilaclient.tests.unit import utils from manilaclient.tests.unit import utils
from manilaclient.tests.unit.v2 import fakes from manilaclient.tests.unit.v2 import fakes
from manilaclient.v2 import share_servers from manilaclient.v2 import share_servers
@ -65,6 +68,7 @@ class ShareServerTest(utils.TestCase):
"has not been raised.") "has not been raised.")
@ddt.ddt
class ShareServerManagerTest(utils.TestCase): class ShareServerManagerTest(utils.TestCase):
def setUp(self): def setUp(self):
@ -79,6 +83,69 @@ class ShareServerManagerTest(utils.TestCase):
share_servers.RESOURCES_PATH, share_servers.RESOURCES_PATH,
share_servers.RESOURCES_NAME) share_servers.RESOURCES_NAME)
@ddt.data(None, {}, {'opt1': 'fake_opt1', 'opt12': 'fake_opt2'})
def test_manage(self, driver_options):
host = 'fake_host'
share_network_id = 'fake_share_net_id'
identifier = 'ff-aa-kk-ee-00'
if driver_options is None:
driver_options = {}
expected_body = {
'host': host,
'share_network_id': share_network_id,
'identifier': identifier,
'driver_options': driver_options
}
with mock.patch.object(self.manager, '_create',
mock.Mock(return_value='fake')):
result = self.manager.manage(host, share_network_id, identifier,
driver_options)
self.manager._create.assert_called_once_with(
share_servers.RESOURCES_PATH + '/manage',
{'share_server': expected_body}, 'share_server'
)
self.assertEqual('fake', result)
@ddt.data(True, False)
def test_unmanage(self, force):
share_server = {'id': 'fake'}
with mock.patch.object(self.manager, '_action',
mock.Mock(return_value='fake')):
result = self.manager.unmanage(share_server, force)
self.manager._action.assert_called_once_with(
"unmanage", share_server, {'force': force})
self.assertEqual('fake', result)
def test_reset_state(self):
share_server = {'id': 'fake'}
state = constants.STATUS_AVAILABLE
with mock.patch.object(self.manager, '_action',
mock.Mock(return_value='fake')):
result = self.manager.reset_state(share_server, state)
self.manager._action.assert_called_once_with(
"reset_status", share_server, {"status": state})
self.assertEqual('fake', result)
@ddt.data(("reset_status", {"status": constants.STATUS_AVAILABLE}),
("unmanage", {"id": "fake_id"}))
@ddt.unpack
def test__action(self, action, info):
action = ""
share_server = {"id": 'fake_id'}
expected_url = '/share-servers/%s/action' % share_server['id']
expected_body = {action: info}
with mock.patch.object(self.manager.api.client, 'post',
mock.Mock(return_value='fake')):
self.mock_object(common_base, 'getid',
mock.Mock(return_value=share_server['id']))
result = self.manager._action(action, share_server, info)
self.manager.api.client.post.assert_called_once_with(
expected_url, body=expected_body
)
self.assertEqual('fake', result)
def test_list_with_one_search_opt(self): def test_list_with_one_search_opt(self):
host = 'fake_host' host = 'fake_host'
query_string = "?host=%s" % host query_string = "?host=%s" % host

View File

@ -162,9 +162,11 @@ class SharesTest(utils.TestCase):
("2.7", "/shares/manage", None), ("2.7", "/shares/manage", None),
("2.8", "/shares/manage", True), ("2.8", "/shares/manage", True),
("2.8", "/shares/manage", False), ("2.8", "/shares/manage", False),
("2.49", "/shares/manage", False, '1234'),
) )
@ddt.unpack @ddt.unpack
def test_manage_share(self, microversion, resource_path, is_public=False): def test_manage_share(self, microversion, resource_path, is_public=False,
share_server_id=None):
service_host = "fake_service_host" service_host = "fake_service_host"
protocol = "fake_protocol" protocol = "fake_protocol"
export_path = "fake_export_path" export_path = "fake_export_path"
@ -180,6 +182,7 @@ class SharesTest(utils.TestCase):
"driver_options": driver_options, "driver_options": driver_options,
"name": name, "name": name,
"description": description, "description": description,
"share_server_id": share_server_id,
} }
version = api_versions.APIVersion(microversion) version = api_versions.APIVersion(microversion)
if version >= api_versions.APIVersion('2.8'): if version >= api_versions.APIVersion('2.8'):
@ -190,15 +193,19 @@ class SharesTest(utils.TestCase):
with mock.patch.object(manager, "_create", with mock.patch.object(manager, "_create",
mock.Mock(return_value="fake")): mock.Mock(return_value="fake")):
if version < api_versions.APIVersion('2.8'):
if version >= api_versions.APIVersion('2.8'): result = manager.manage(
service_host, protocol, export_path, driver_options,
share_type, name, description)
elif (api_versions.APIVersion('2.8') <= version
< api_versions.APIVersion('2.49')):
result = manager.manage( result = manager.manage(
service_host, protocol, export_path, driver_options, service_host, protocol, export_path, driver_options,
share_type, name, description, is_public) share_type, name, description, is_public)
else: else:
result = manager.manage( result = manager.manage(
service_host, protocol, export_path, driver_options, service_host, protocol, export_path, driver_options,
share_type, name, description) share_type, name, description, is_public, share_server_id)
self.assertEqual(manager._create.return_value, result) self.assertEqual(manager._create.return_value, result)
manager._create.assert_called_once_with( manager._create.assert_called_once_with(

View File

@ -637,21 +637,25 @@ class ShellTest(test_utils.TestCase):
'valid_params': { 'valid_params': {
'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'}, 'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'},
'share_type': 'fake_share_type', 'share_type': 'fake_share_type',
'share_server_id': None,
}}, }},
{'cmd_args': '--share_type fake_share_type', {'cmd_args': '--share_type fake_share_type',
'valid_params': { 'valid_params': {
'driver_options': {}, 'driver_options': {},
'share_type': 'fake_share_type', 'share_type': 'fake_share_type',
'share_server_id': None,
}}, }},
{'cmd_args': '', {'cmd_args': '',
'valid_params': { 'valid_params': {
'driver_options': {}, 'driver_options': {},
'share_type': None, 'share_type': None,
'share_server_id': None,
}}, }},
{'cmd_args': '--public', {'cmd_args': '--public',
'valid_params': { 'valid_params': {
'driver_options': {}, 'driver_options': {},
'share_type': None, 'share_type': None,
'share_server_id': None,
}, },
'is_public': True, 'is_public': True,
'version': '--os-share-api-version 2.8', 'version': '--os-share-api-version 2.8',
@ -660,10 +664,38 @@ class ShellTest(test_utils.TestCase):
'valid_params': { 'valid_params': {
'driver_options': {}, 'driver_options': {},
'share_type': None, 'share_type': None,
'share_server_id': None,
}, },
'is_public': False, 'is_public': False,
'version': '--os-share-api-version 2.8', 'version': '--os-share-api-version 2.8',
}, },
{'cmd_args': '--driver_options opt1=opt1 opt2=opt2'
' --share_type fake_share_type',
'valid_params': {
'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'},
'share_type': 'fake_share_type',
'share_server_id': None,
},
'version': '--os-share-api-version 2.49',
},
{'cmd_args': '--driver_options opt1=opt1 opt2=opt2'
' --share_type fake_share_type'
' --share_server_id fake_server',
'valid_params': {
'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'},
'share_type': 'fake_share_type',
'share_server_id': 'fake_server',
},
'version': '--os-share-api-version 2.49',
},
{'cmd_args': '--driver_options opt1=opt1 opt2=opt2'
' --share_type fake_share_type'
' --share_server_id fake_server',
'valid_params': {
'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'},
'share_type': 'fake_share_type',
'share_server_id': 'fake_server',
}},
) )
@ddt.unpack @ddt.unpack
def test_manage(self, cmd_args, valid_params, is_public=False, def test_manage(self, cmd_args, valid_params, is_public=False,
@ -685,15 +717,88 @@ class ShellTest(test_utils.TestCase):
'name': None, 'name': None,
'description': None, 'description': None,
'is_public': is_public, 'is_public': is_public,
'share_server_id': valid_params['share_server_id'],
} }
} }
expected['share'].update(valid_params) expected['share'].update(valid_params)
self.assert_called('POST', '/shares/manage', body=expected) self.assert_called('POST', '/shares/manage', body=expected)
def test_manage_invalid_param_share_server_id(self):
self.assertRaises(
exceptions.CommandError,
self.run_command,
'--os-share-api-version 2.48'
+ ' manage fake_service fake_protocol '
+ ' fake_export_path '
+ ' --driver_options opt1=opt1 opt2=opt2'
+ ' --share_type fake_share_type'
+ ' --share_server_id fake_server')
@ddt.data({'driver_args': '--driver_options opt1=opt1 opt2=opt2',
'valid_params': {
'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'},
},
'version': '--os-share-api-version 2.49',
},
{'driver_args': '--driver_options opt1=opt1 opt2=opt2',
'valid_params': {
'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'},
},
},
{'driver_args': "",
'valid_params': {
'driver_options': {}
},
'version': '--os-share-api-version 2.49',
})
@ddt.unpack
def test_share_server_manage(self, driver_args, valid_params,
version=None):
fake_share_network = type(
'FakeShareNetwork', (object,), {'id': '3456'})
self.mock_object(
shell_v2, '_find_share_network',
mock.Mock(return_value=fake_share_network))
command = "" if version is None else version
command += (' share-server-manage fake_host fake_share_net_id '
+ ' 88-as-23-f3-45 ' + driver_args)
self.run_command(command)
expected = {
'share_server': {
'host': 'fake_host',
'share_network_id': fake_share_network.id,
'identifier': '88-as-23-f3-45',
'driver_options': driver_args
}
}
expected['share_server'].update(valid_params)
self.assert_called('POST', '/share-servers/manage', body=expected)
@ddt.data(constants.STATUS_ERROR, constants.STATUS_ACTIVE,
constants.STATUS_MANAGE_ERROR, constants.STATUS_UNMANAGE_ERROR,
constants.STATUS_DELETING, constants.STATUS_CREATING)
def test_share_server_reset_state(self, status):
self.run_command('share-server-reset-state 1234 --state %s ' % status)
expected = {'reset_status': {'status': status}}
self.assert_called('POST', '/share-servers/1234/action', body=expected)
def test_unmanage(self): def test_unmanage(self):
self.run_command('unmanage 1234') self.run_command('unmanage 1234')
self.assert_called('POST', '/shares/1234/action') self.assert_called('POST', '/shares/1234/action')
def test_share_server_unmanage(self):
self.run_command('share-server-unmanage 1234')
self.assert_called('POST', '/share-servers/1234/action',
body={'unmanage': {'force': False}})
def test_share_server_unmanage_force(self):
self.run_command('share-server-unmanage 1234 --force')
self.assert_called('POST', '/share-servers/1234/action',
body={'unmanage': {'force': True}})
@ddt.data({'cmd_args': '--driver_options opt1=opt1 opt2=opt2', @ddt.data({'cmd_args': '--driver_options opt1=opt1 opt2=opt2',
'valid_params': { 'valid_params': {
'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'}, 'driver_options': {'opt1': 'opt1', 'opt2': 'opt2'},
@ -3116,3 +3221,18 @@ class ShellTest(test_utils.TestCase):
cmd + option + separator + 'fake', cmd + option + separator + 'fake',
version=version version=version
) )
def test_share_server_unmanage_all_fail(self):
# All of 2345, 5678, 9999 throw exception
cmd = '--os-share-api-version 2.49'
cmd += ' share-server-unmanage'
cmd += ' 2345 5678 9999'
self.assertRaises(exceptions.CommandError,
self.run_command, cmd)
def test_share_server_unmanage_some_fail(self):
# 5678 and 9999 throw exception
self.run_command('share-server-unmanage 1234 5678 9999')
expected = {'unmanage': {'force': False}}
self.assert_called('POST', '/share-servers/1234/action',
body=expected)

View File

@ -13,13 +13,15 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from manilaclient import api_versions
from manilaclient import base from manilaclient import base
from manilaclient.common.apiclient import base as common_base from manilaclient.common.apiclient import base as common_base
RESOURCES_PATH = '/share-servers'
RESOURCE_PATH = '/share-servers/%s'
RESOURCES_NAME = 'share_servers' RESOURCES_NAME = 'share_servers'
RESOURCES_PATH = '/share-servers'
RESOURCE_PATH = RESOURCES_PATH + '/%s'
RESOURCE_NAME = 'share_server' RESOURCE_NAME = 'share_server'
ACTION_PATH = RESOURCE_PATH + '/action'
class ShareServer(common_base.Resource): class ShareServer(common_base.Resource):
@ -36,6 +38,14 @@ class ShareServer(common_base.Resource):
"""Delete this share server.""" """Delete this share server."""
self.manager.delete(self) self.manager.delete(self)
def unmanage(self, force=False):
"""Unmanage this share server."""
self.manager.unmanage(self, force)
def reset_state(self, state):
"""Update the share server with the provided state."""
self.manager.reset_state(self, state)
class ShareServerManager(base.ManagerWithFind): class ShareServerManager(base.ManagerWithFind):
"""Manage :class:`ShareServer` resources.""" """Manage :class:`ShareServer` resources."""
@ -86,3 +96,44 @@ class ShareServerManager(base.ManagerWithFind):
""" """
query_string = self._build_query_string(search_opts) query_string = self._build_query_string(search_opts)
return self._list(RESOURCES_PATH + query_string, RESOURCES_NAME) return self._list(RESOURCES_PATH + query_string, RESOURCES_NAME)
@api_versions.wraps("2.49")
def manage(self, host, share_network_id, identifier, driver_options=None):
driver_options = driver_options or {}
body = {
'host': host,
'share_network_id': share_network_id,
'identifier': identifier,
'driver_options': driver_options,
}
resource_path = RESOURCE_PATH % 'manage'
return self._create(resource_path, {'share_server': body},
'share_server')
@api_versions.wraps("2.49")
def unmanage(self, share_server, force=False):
return self._action("unmanage", share_server, {'force': force})
@api_versions.wraps("2.49")
def reset_state(self, share_server, state):
"""Update the provided share server with the provided state.
:param share_server: either share_server object or text with its ID.
:param state: text with new state to set for share.
"""
return self._action("reset_status", share_server, {"status": state})
def _action(self, action, share_server, info=None):
"""Perform a share server 'action'.
:param action: text with action name.
:param share_server: either share_server object or text with its ID.
:param info: dict with data for specified 'action'.
:param kwargs: dict with data to be provided for action hooks.
"""
body = {action: info}
self.run_hooks('modify_body_for_action', body)
url = ACTION_PATH % common_base.getid(share_server)
return self.api.client.post(url, body=body)

View File

@ -215,7 +215,7 @@ class ShareManager(base.ManagerWithFind):
def _do_manage(self, service_host, protocol, export_path, def _do_manage(self, service_host, protocol, export_path,
driver_options=None, share_type=None, driver_options=None, share_type=None,
name=None, description=None, is_public=None, name=None, description=None, is_public=None,
resource_path="/shares/manage"): share_server_id=None, resource_path="/shares/manage"):
"""Manage some existing share. """Manage some existing share.
:param service_host: text - host where manila share service is running :param service_host: text - host where manila share service is running
@ -226,6 +226,7 @@ class ShareManager(base.ManagerWithFind):
:param name: text - name of new share :param name: text - name of new share
:param description: - description for new share :param description: - description for new share
:param is_public: - visibility for new share :param is_public: - visibility for new share
:param share_server_id: text - id of share server associated with share
""" """
driver_options = driver_options if driver_options else dict() driver_options = driver_options if driver_options else dict()
body = { body = {
@ -236,6 +237,7 @@ class ShareManager(base.ManagerWithFind):
'driver_options': driver_options, 'driver_options': driver_options,
'name': name, 'name': name,
'description': description, 'description': description,
'share_server_id': share_server_id,
} }
if is_public is not None: if is_public is not None:
@ -246,25 +248,36 @@ class ShareManager(base.ManagerWithFind):
@api_versions.wraps("1.0", "2.6") @api_versions.wraps("1.0", "2.6")
def manage(self, service_host, protocol, export_path, driver_options=None, def manage(self, service_host, protocol, export_path, driver_options=None,
share_type=None, name=None, description=None): share_type=None, name=None, description=None):
is_public = None
return self._do_manage( return self._do_manage(
service_host, protocol, export_path, driver_options, share_type, service_host, protocol, export_path, driver_options=driver_options,
name, description, is_public, resource_path="/os-share-manage") share_type=share_type, name=name, description=description,
resource_path="/os-share-manage")
@api_versions.wraps("2.7", "2.7") # noqa @api_versions.wraps("2.7", "2.7") # noqa
def manage(self, service_host, protocol, export_path, driver_options=None, def manage(self, service_host, protocol, export_path, driver_options=None,
share_type=None, name=None, description=None): share_type=None, name=None, description=None):
is_public = None
return self._do_manage( return self._do_manage(
service_host, protocol, export_path, driver_options, share_type, service_host, protocol, export_path, driver_options=driver_options,
name, description, is_public, resource_path="/shares/manage") share_type=share_type, name=name, description=description,
resource_path="/shares/manage")
@api_versions.wraps("2.8") # noqa @api_versions.wraps("2.8", "2.48") # noqa
def manage(self, service_host, protocol, export_path, driver_options=None, def manage(self, service_host, protocol, export_path, driver_options=None,
share_type=None, name=None, description=None, is_public=False): share_type=None, name=None, description=None, is_public=False):
return self._do_manage( return self._do_manage(
service_host, protocol, export_path, driver_options, share_type, service_host, protocol, export_path, driver_options=driver_options,
name, description, is_public, "/shares/manage") share_type=share_type, name=name, description=description,
is_public=is_public, resource_path="/shares/manage")
@api_versions.wraps("2.49") # noqa
def manage(self, service_host, protocol, export_path, driver_options=None,
share_type=None, name=None, description=None, is_public=False,
share_server_id=None):
return self._do_manage(
service_host, protocol, export_path, driver_options=driver_options,
share_type=share_type, name=name, description=description,
is_public=is_public, share_server_id=share_server_id,
resource_path="/shares/manage")
@api_versions.wraps("1.0", "2.6") @api_versions.wraps("1.0", "2.6")
def unmanage(self, share): def unmanage(self, share):

View File

@ -1120,20 +1120,95 @@ def do_share_export_location_show(cs, args):
help="Level of visibility for share. Defines whether other tenants are " help="Level of visibility for share. Defines whether other tenants are "
"able to see it or not. Available only for microversion >= 2.8. " "able to see it or not. Available only for microversion >= 2.8. "
"(Default=False)") "(Default=False)")
@cliutils.arg(
'--share_server_id', '--share-server-id',
metavar='<share-server-id>',
default=None,
action='single_alias',
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)")
def do_manage(cs, args): def do_manage(cs, args):
"""Manage share not handled by Manila (Admin only).""" """Manage share not handled by Manila (Admin only)."""
driver_options = _extract_key_value_options(args, 'driver_options') driver_options = _extract_key_value_options(args, 'driver_options')
if cs.api_version.matches(api_versions.APIVersion("2.49"),
share = cs.shares.manage( api_versions.APIVersion()):
args.service_host, args.protocol, args.export_path, share = cs.shares.manage(
driver_options=driver_options, share_type=args.share_type, args.service_host, args.protocol, args.export_path,
name=args.name, description=args.description, driver_options=driver_options, share_type=args.share_type,
is_public=args.public, name=args.name, description=args.description,
) is_public=args.public, share_server_id=args.share_server_id)
else:
if args.share_server_id:
raise exceptions.CommandError("Invalid parameter "
"--share_server_id specified. This"
" parameter is only supported on"
" microversion 2.49 or newer.")
share = cs.shares.manage(
args.service_host, args.protocol, args.export_path,
driver_options=driver_options, share_type=args.share_type,
name=args.name, description=args.description,
is_public=args.public)
_print_share(cs, share) _print_share(cs, share)
@api_versions.wraps("2.49")
@cliutils.arg(
'host',
metavar='<host>',
type=str,
help='Backend name as "<node_hostname>@<backend_name>".')
@cliutils.arg(
'share_network',
metavar='<share_network>',
help="Share network where share server has network allocations in.")
@cliutils.arg(
'identifier',
metavar='<identifier>',
type=str,
help='A driver-specific share server identifier required by the driver to '
'manage the share server.')
@cliutils.arg(
'--driver_options', '--driver-options',
type=str,
nargs='*',
metavar='<key=value>',
action='single_alias',
help='One or more driver-specific key=value pairs that may be necessary to'
' manage the share server (Optional, Default=None).',
default=None)
def do_share_server_manage(cs, args):
"""Manage share server not handled by Manila (Admin only)."""
driver_options = _extract_key_value_options(args, 'driver_options')
share_network = _find_share_network(cs, args.share_network)
share_server = cs.share_servers.manage(
args.host, share_network.id, args.identifier,
driver_options=driver_options)
cliutils.print_dict(share_server._info)
@cliutils.arg(
'share_server_id',
metavar='<share_server_id>',
help='ID of the share server to modify.')
@cliutils.arg(
'--state',
metavar='<state>',
default=constants.STATUS_ACTIVE,
help=('Indicate which state to assign the share server. Options include '
'active, error, creating, deleting, managing, unmanaging, '
'manage_error and unmanage_error. If no state is provided, active '
'will be used.'))
@api_versions.wraps("2.49")
def do_share_server_reset_state(cs, args):
"""Explicitly update the state of a share server (Admin only)."""
cs.share_servers.reset_state(args.share_server_id, args.state)
@api_versions.wraps("2.12") @api_versions.wraps("2.12")
@cliutils.arg( @cliutils.arg(
'share', 'share',
@ -1188,6 +1263,36 @@ def do_unmanage(cs, args):
share_ref.unmanage() share_ref.unmanage()
@api_versions.wraps("2.49")
@cliutils.arg(
'share_server',
metavar='<share_server>',
nargs='+',
help='ID of the share server(s).')
@cliutils.arg(
'--force',
dest='force',
action="store_true",
required=False,
default=False,
help="Enforces the unmanage share server operation, even if the back-end "
"driver does not support it.")
def do_share_server_unmanage(cs, args):
"""Unmanage share server (Admin only)."""
failure_count = 0
for server in args.share_server:
try:
cs.share_servers.unmanage(server, args.force)
except Exception as e:
failure_count += 1
print("Unmanage for share server %s failed: %s" % (server, e),
file=sys.stderr)
if failure_count == len(args.share_server):
raise exceptions.CommandError("Unable to unmanage any of the "
"specified share servers.")
@api_versions.wraps("2.12") @api_versions.wraps("2.12")
@cliutils.arg( @cliutils.arg(
'snapshot', 'snapshot',

View File

@ -0,0 +1,6 @@
---
features:
- Added CLI commands to manage and unmanage share servers.
- Updated CLI command for managing shares to accept
``share_server_id`` parameter.