Merge "Implement manage/unmanage support in generic driver"

This commit is contained in:
Jenkins 2015-03-19 04:53:53 +00:00 committed by Gerrit Code Review
commit 20e590e8b3
15 changed files with 599 additions and 71 deletions

View File

@ -40,6 +40,9 @@ iniset $BASE/new/tempest/etc/tempest.conf share share_creation_retry_number 2
# Suppress errors in cleanup of resources # Suppress errors in cleanup of resources
iniset $BASE/new/tempest/etc/tempest.conf share suppress_errors_in_cleanup True iniset $BASE/new/tempest/etc/tempest.conf share suppress_errors_in_cleanup True
# Enable manage/unmanage tests
iniset $BASE/new/tempest/etc/tempest.conf share run_manage_unmanage_tests True
# Define whether share drivers handle share servers or not. # Define whether share drivers handle share servers or not.
# Requires defined config option 'driver_handles_share_servers'. # Requires defined config option 'driver_handles_share_servers'.
MANILA_CONF=${MANILA_CONF:-/etc/manila/manila.conf} MANILA_CONF=${MANILA_CONF:-/etc/manila/manila.conf}

View File

@ -0,0 +1,145 @@
# Copyright 2015 Mirantis Inc.
# 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 import exceptions as lib_exc # noqa
import testtools # noqa
from tempest.api.share import base
from tempest.common.utils import data_utils
from tempest import config_share as config
from tempest import test
CONF = config.CONF
class ManageNFSShareTest(base.BaseSharesAdminTest):
protocol = 'nfs'
# NOTE(vponomaryov): be careful running these tests using generic driver
# because cinder volumes will stay attached to service Nova VM and
# won't be deleted.
@classmethod
@testtools.skipIf(
CONF.share.multitenancy_enabled,
"Only for driver_handles_share_servers = False driver mode.")
@testtools.skipUnless(
CONF.share.run_manage_unmanage_tests,
"Manage/unmanage tests are disabled.")
def resource_setup(cls):
super(ManageNFSShareTest, cls).resource_setup()
if cls.protocol not in CONF.share.enable_protocols:
message = "%s tests are disabled" % cls.protocol
raise cls.skipException(message)
# Create share types
cls.st_name = data_utils.rand_name("manage-st-name")
cls.st_name_invalid = data_utils.rand_name("manage-st-name-invalid")
cls.extra_specs = {
'storage_protocol': CONF.share.storage_protocol,
'driver_handles_share_servers': False
}
cls.extra_specs_invalid = {
'storage_protocol': CONF.share.storage_protocol,
'driver_handles_share_servers': True
}
__, cls.st = cls.create_share_type(
name=cls.st_name,
cleanup_in_class=True,
extra_specs=cls.extra_specs)
__, cls.st_invalid = cls.create_share_type(
name=cls.st_name_invalid,
cleanup_in_class=True,
extra_specs=cls.extra_specs_invalid)
creation_data = {'kwargs': {
'share_type_id': cls.st['share_type']['id'],
'share_protocol': cls.protocol,
}}
# Create two shares in parallel
cls.shares = cls.create_shares([creation_data, creation_data])
# Load all share data (host, etc.)
__, cls.share1 = cls.shares_client.get_share(cls.shares[0]['id'])
__, cls.share2 = cls.shares_client.get_share(cls.shares[1]['id'])
# Unmanage shares from manila
cls.shares_client.unmanage_share(cls.share1['id'])
cls.shares_client.wait_for_resource_deletion(share_id=cls.share1['id'])
cls.shares_client.unmanage_share(cls.share2['id'])
cls.shares_client.wait_for_resource_deletion(share_id=cls.share2['id'])
@test.attr(type=["gate", "smoke"])
def test_manage(self):
# manage share
resp, share = self.shares_client.manage_share(
service_host=self.share1['host'],
export_path=self.share1['export_locations'][0],
protocol=self.share1['share_proto'],
share_type_id=self.st['share_type']['id'])
self.assertIn(int(resp["status"]), self.HTTP_SUCCESS)
# Add managed share to cleanup queue
self.method_resources.insert(
0, {'type': 'share_type', 'id': share['id'],
'client': self.shares_client})
# Wait for success
self.shares_client.wait_for_share_status(share['id'], 'available')
# delete share
resp, __ = self.shares_client.delete_share(share['id'])
self.assertIn(int(resp["status"]), self.HTTP_SUCCESS)
self.shares_client.wait_for_resource_deletion(share_id=share['id'])
self.assertRaises(lib_exc.NotFound,
self.shares_client.get_share,
share['id'])
@test.attr(type=["gate", "smoke"])
def test_manage_retry(self):
# manage share with invalid parameters
share = None
parameters = [(self.st_invalid['share_type']['id'], 'MANAGE_ERROR'),
(self.st['share_type']['id'], 'available')]
for share_type_id, status in parameters:
resp, share = self.shares_client.manage_share(
service_host=self.share2['host'],
export_path=self.share2['export_locations'][0],
protocol=self.share2['share_proto'],
share_type_id=share_type_id)
self.assertIn(int(resp["status"]), self.HTTP_SUCCESS)
# Add managed share to cleanup queue
self.method_resources.insert(
0, {'type': 'share_type', 'id': share['id'],
'client': self.shares_client})
# Wait for success
self.shares_client.wait_for_share_status(share['id'], status)
# delete share
resp, __ = self.shares_client.delete_share(share['id'])
self.assertIn(int(resp["status"]), self.HTTP_SUCCESS)
self.shares_client.wait_for_resource_deletion(share_id=share['id'])
self.assertRaises(lib_exc.NotFound,
self.shares_client.get_share,
share['id'])
class ManageCIFSShareTest(ManageNFSShareTest):
protocol = 'cifs'

View File

@ -108,6 +108,11 @@ ShareGroup = [
help="Whether to suppress errors with clean up operation " help="Whether to suppress errors with clean up operation "
"or not. There are cases when we may want to skip " "or not. There are cases when we may want to skip "
"such errors and catch only test errors."), "such errors and catch only test errors."),
cfg.BoolOpt("run_manage_unmanage_tests",
default=False,
help="Defines whether to run manage/unmanage tests or not. "
"These test may leave orphaned resources, so be careful "
"enabling this opt."),
] ]

View File

@ -84,6 +84,23 @@ class SharesClient(service_client.ServiceClient):
def delete_share(self, share_id): def delete_share(self, share_id):
return self.delete("shares/%s" % share_id) return self.delete("shares/%s" % share_id)
def manage_share(self, service_host, protocol, export_path,
share_type_id):
post_body = {
"share": {
"export_path": export_path,
"service_host": service_host,
"protocol": protocol,
"share_type": share_type_id,
}
}
body = json.dumps(post_body)
resp, body = self.post("os-share-manage", body)
return resp, self._parse_resp(body)
def unmanage_share(self, share_id):
return self.post("os-share-unmanage/%s/unmanage" % share_id, None)
def list_shares(self, detailed=False, params=None): def list_shares(self, detailed=False, params=None):
"""Get list of shares w/o filters.""" """Get list of shares w/o filters."""
uri = 'shares/detail' if detailed else 'shares' uri = 'shares/detail' if detailed else 'shares'
@ -176,7 +193,7 @@ class SharesClient(service_client.ServiceClient):
share_status = body['status'] share_status = body['status']
if share_status == status: if share_status == status:
return return
elif 'error' in share_status: elif 'error' in share_status.lower():
raise share_exceptions.\ raise share_exceptions.\
ShareBuildErrorException(share_id=share_id) ShareBuildErrorException(share_id=share_id)

View File

@ -13,11 +13,11 @@
# under the License. # under the License.
import six import six
import webob
from webob import exc from webob import exc
from manila.api import extensions from manila.api import extensions
from manila.api.openstack import wsgi from manila.api.openstack import wsgi
from manila.api.views import shares as share_views
from manila import exception from manila import exception
from manila.i18n import _ from manila.i18n import _
from manila import share from manila import share
@ -29,6 +29,9 @@ authorize = extensions.extension_authorizer('share', 'manage')
class ShareManageController(wsgi.Controller): class ShareManageController(wsgi.Controller):
_view_builder_class = share_views.ViewBuilder
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ShareManageController, self).__init__(*args, **kwargs) super(ShareManageController, self).__init__(*args, **kwargs)
self.share_api = share.API() self.share_api = share.API()
@ -41,7 +44,7 @@ class ShareManageController(wsgi.Controller):
share = { share = {
'host': share_data['service_host'], 'host': share_data['service_host'],
'export_location': share_data['export_path'], 'export_location': share_data['export_path'],
'share_proto': share_data['protocol'], 'share_proto': share_data['protocol'].upper(),
'share_type_id': share_data['share_type_id'], 'share_type_id': share_data['share_type_id'],
'display_name': share_data.get('display_name', ''), 'display_name': share_data.get('display_name', ''),
'display_description': share_data.get('display_description', ''), 'display_description': share_data.get('display_description', ''),
@ -50,13 +53,13 @@ class ShareManageController(wsgi.Controller):
driver_options = share_data.get('driver_options', {}) driver_options = share_data.get('driver_options', {})
try: try:
self.share_api.manage(context, share, driver_options) share_ref = self.share_api.manage(context, share, driver_options)
except exception.PolicyNotAuthorized as e: except exception.PolicyNotAuthorized as e:
raise exc.HTTPForbidden(explanation=six.text_type(e)) raise exc.HTTPForbidden(explanation=six.text_type(e))
except exception.ManilaException as e: except exception.ManilaException as e:
raise exc.HTTPConflict(explanation=six.text_type(e)) raise exc.HTTPConflict(explanation=six.text_type(e))
return webob.Response(status_int=202) return self._view_builder.detail(req, share_ref)
def _validate_manage_parameters(self, context, body): def _validate_manage_parameters(self, context, body):
if not (body and self.is_valid_body(body, 'share')): if not (body and self.is_valid_body(body, 'share')):
@ -71,6 +74,9 @@ class ShareManageController(wsgi.Controller):
if parameter not in data: if parameter not in data:
msg = _("Required parameter %s not found") % parameter msg = _("Required parameter %s not found") % parameter
raise exc.HTTPUnprocessableEntity(explanation=msg) raise exc.HTTPUnprocessableEntity(explanation=msg)
if not data.get(parameter):
msg = _("Required parameter %s is empty") % parameter
raise exc.HTTPUnprocessableEntity(explanation=msg)
if not share_utils.extract_host(data['service_host'], 'pool'): if not share_utils.extract_host(data['service_host'], 'pool'):
msg = _("service_host parameter should contain pool.") msg = _("service_host parameter should contain pool.")
@ -114,4 +120,4 @@ class Share_manage(extensions.ExtensionDescriptor):
controller = ShareManageController() controller = ShareManageController()
res = extensions.ResourceExtension(Share_manage.alias, res = extensions.ResourceExtension(Share_manage.alias,
controller) controller)
return [res] return [res]

View File

@ -163,6 +163,10 @@ class InvalidUUID(Invalid):
message = _("Expected a uuid but received %(uuid)s.") message = _("Expected a uuid but received %(uuid)s.")
class InvalidDriverMode(Invalid):
message = _("Invalid driver mode: %(driver_mode)s.")
class NotFound(ManilaException): class NotFound(ManilaException):
message = _("Resource could not be found.") message = _("Resource could not be found.")
code = 404 code = 404

View File

@ -230,11 +230,13 @@ class API(base.Base):
'project_id': context.project_id, 'project_id': context.project_id,
'status': constants.STATUS_MANAGING, 'status': constants.STATUS_MANAGING,
'scheduled_at': timeutils.utcnow(), 'scheduled_at': timeutils.utcnow(),
'deleted': False,
}) })
retry_states = (constants.STATUS_MANAGE_ERROR, LOG.debug("Manage: Found shares %s" % len(shares))
constants.STATUS_UNMANAGED)
retry_states = (constants.STATUS_MANAGE_ERROR,)
export_location = share_data.pop('export_location')
if len(shares) == 0: if len(shares) == 0:
share = self.db.share_create(context, share_data) share = self.db.share_create(context, share_data)
@ -246,7 +248,11 @@ class API(base.Base):
msg = _("Share already exists.") msg = _("Share already exists.")
raise exception.ManilaException(msg) raise exception.ManilaException(msg)
self.db.share_export_locations_update(context, share['id'],
export_location)
self.share_rpcapi.manage_share(context, share, driver_options) self.share_rpcapi.manage_share(context, share, driver_options)
return self.db.share_get(context, share['id'])
def unmanage(self, context, share): def unmanage(self, context, share):
policy.check_policy(context, 'share', 'unmanage') policy.check_policy(context, 'share', 'unmanage')

View File

@ -24,6 +24,7 @@ from oslo_config import cfg
from oslo_log import log from oslo_log import log
from oslo_utils import excutils from oslo_utils import excutils
from oslo_utils import importutils from oslo_utils import importutils
from oslo_utils import strutils
import six import six
from manila.common import constants as const from manila.common import constants as const
@ -35,6 +36,7 @@ from manila.i18n import _LE
from manila.i18n import _LW from manila.i18n import _LW
from manila.share import driver from manila.share import driver
from manila.share.drivers import service_instance from manila.share.drivers import service_instance
from manila.share import share_types
from manila import utils from manila import utils
from manila import volume from manila import volume
@ -206,9 +208,8 @@ class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
volume['mountpoint']] volume['mountpoint']]
self._ssh_exec(server_details, command) self._ssh_exec(server_details, command)
def _is_device_mounted(self, share, server_details, volume=None): def _is_device_mounted(self, mount_path, server_details, volume=None):
"""Checks whether volume already mounted or not.""" """Checks whether volume already mounted or not."""
mount_path = self._get_mount_path(share)
log_data = { log_data = {
'mount_path': mount_path, 'mount_path': mount_path,
'server_id': server_details['instance_id'], 'server_id': server_details['instance_id'],
@ -274,7 +275,8 @@ class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
'server': server_details['instance_id'], 'server': server_details['instance_id'],
} }
try: try:
if not self._is_device_mounted(share, server_details, volume): if not self._is_device_mounted(mount_path, server_details,
volume):
LOG.debug("Mounting '%(dev)s' to path '%(path)s' on " LOG.debug("Mounting '%(dev)s' to path '%(path)s' on "
"server '%(server)s'.", log_data) "server '%(server)s'.", log_data)
mount_cmd = ['sudo mkdir -p', mount_path, '&&'] mount_cmd = ['sudo mkdir -p', mount_path, '&&']
@ -303,7 +305,7 @@ class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
'path': mount_path, 'path': mount_path,
'server': server_details['instance_id'], 'server': server_details['instance_id'],
} }
if self._is_device_mounted(share, server_details): if self._is_device_mounted(mount_path, server_details):
LOG.debug("Unmounting path '%(path)s' on server " LOG.debug("Unmounting path '%(path)s' on server "
"'%(server)s'.", log_data) "'%(server)s'.", log_data)
unmount_cmd = ['sudo umount', mount_path, '&& sudo rmdir', unmount_cmd = ['sudo umount', mount_path, '&& sudo rmdir',
@ -354,24 +356,28 @@ class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
self.configuration.max_time_to_attach) self.configuration.max_time_to_attach)
return do_attach(volume) return do_attach(volume)
def _get_volume_name(self, share_id):
return self.configuration.volume_name_template % share_id
def _get_volume(self, context, share_id): def _get_volume(self, context, share_id):
"""Finds volume, associated to the specific share.""" """Finds volume, associated to the specific share."""
volume_name = self.configuration.volume_name_template % share_id volume_name = self._get_volume_name(share_id)
search_opts = {'name': volume_name} search_opts = {'name': volume_name}
if context.is_admin: if context.is_admin:
search_opts['all_tenants'] = True search_opts['all_tenants'] = True
volumes_list = self.volume_api.get_all(context, search_opts) volumes_list = self.volume_api.get_all(context, search_opts)
volume = None
if len(volumes_list) == 1: if len(volumes_list) == 1:
volume = volumes_list[0] return volumes_list[0]
elif len(volumes_list) > 1: elif len(volumes_list) > 1:
LOG.error( LOG.error(
_LE("Expected only one volume in volume list with name " _LE("Expected only one volume in volume list with name "
"'%(name)s', but got more than one in a result - " "'%(name)s', but got more than one in a result - "
"'%(result)s'."), { "'%(result)s'."), {
'name': volume_name, 'result': volumes_list}) 'name': volume_name, 'result': volumes_list})
raise exception.ManilaException(_('Error. Ambiguous volumes')) raise exception.ManilaException(
return volume _("Error. Ambiguous volumes for name '%s'") % volume_name)
else:
raise exception.VolumeNotFound(volume_id=volume_name)
def _get_volume_snapshot(self, context, snapshot_id): def _get_volume_snapshot(self, context, snapshot_id):
"""Find volume snapshot associated to the specific share snapshot.""" """Find volume snapshot associated to the specific share snapshot."""
@ -633,6 +639,107 @@ class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
self.service_instance_manager.delete_service_instance( self.service_instance_manager.delete_service_instance(
self.admin_context, server_details) self.admin_context, server_details)
def manage_existing(self, share, driver_options):
"""Manage existing share to manila.
Generic driver accepts only one driver_option 'volume_id'.
If an administrator provides this option, then appropriate Cinder
volume will be managed by Manila as well.
:param share: share data
:param driver_options: Empty dict or dict with 'volume_id' option.
:return: dict with share size, example: {'size': 1}
"""
if self.driver_handles_share_servers:
msg = _('Operation "manage" for shares is supported only when '
'driver does not handle share servers.')
raise exception.InvalidDriverMode(msg)
helper = self._get_helper(share)
driver_mode = share_types.get_share_type_extra_specs(
share['share_type_id'],
const.ExtraSpecs.DRIVER_HANDLES_SHARE_SERVERS)
if strutils.bool_from_string(driver_mode):
msg = _("%(mode)s != False") % {
'mode': const.ExtraSpecs.DRIVER_HANDLES_SHARE_SERVERS
}
raise exception.ManageExistingShareTypeMismatch(reason=msg)
share_server = self.service_instance_manager.get_common_server()
server_details = share_server['backend_details']
mount_path = helper.get_share_path_by_export_location(
share_server['backend_details'], share['export_locations'][0])
LOG.debug("Manage: mount path = %s", mount_path)
mounted = self._is_device_mounted(mount_path, server_details)
LOG.debug("Manage: is share mounted = %s", mounted)
if not mounted:
msg = _("Provided share %s is not mounted.") % share['id']
raise exception.ManageInvalidShare(reason=msg)
def get_volume():
if 'volume_id' in driver_options:
try:
return self.volume_api.get(
self.admin_context, driver_options['volume_id'])
except exception.VolumeNotFound as e:
raise exception.ManageInvalidShare(reason=six.text_type(e))
# NOTE(vponomaryov): Manila can only combine volume name by itself,
# nowhere to get volume ID from. Return None since Cinder volume
# names are not unique or fixed, hence, they can not be used for
# sure.
return None
share_volume = get_volume()
if share_volume:
instance_volumes = self.compute_api.instance_volumes_list(
self.admin_context, server_details['instance_id'])
attached_volumes = [vol.id for vol in instance_volumes]
LOG.debug('Manage: attached volumes = %s',
six.text_type(attached_volumes))
if share_volume['id'] not in attached_volumes:
msg = _("Provided volume %s is not attached "
"to service instance.") % share_volume['id']
raise exception.ManageInvalidShare(reason=msg)
linked_volume_name = self._get_volume_name(share['id'])
if share_volume['name'] != linked_volume_name:
LOG.debug('Manage: volume_id = %s' % share_volume['id'])
self.volume_api.update(self.admin_context, share_volume['id'],
{'name': linked_volume_name})
share_size = share_volume['size']
else:
share_size = self._get_mounted_share_size(
mount_path, share_server['backend_details'])
# TODO(vponomaryov): need update export_locations too using driver's
# information.
return {'size': share_size}
def _get_mounted_share_size(self, mount_path, server_details):
share_size_cmd = ['df', '-PBG', mount_path]
output, __ = self._ssh_exec(server_details, share_size_cmd)
lines = output.split('\n')
try:
size = int(lines[1].split()[1][:-1])
except Exception as e:
msg = _("Cannot calculate size of share %(path)s : %(error)s") % {
'path': mount_path,
'error': six.text_type(e)
}
raise exception.ManageInvalidShare(reason=msg)
return size
class NASHelperBase(object): class NASHelperBase(object):
"""Interface to work with share.""" """Interface to work with share."""
@ -662,6 +769,10 @@ class NASHelperBase(object):
"""Deny access to the host.""" """Deny access to the host."""
raise NotImplementedError() raise NotImplementedError()
def get_share_path_by_export_location(self, server, export_location):
"""Returns share path by its export location."""
raise NotImplementedError()
def nfs_synchronized(f): def nfs_synchronized(f):
@ -745,6 +856,9 @@ class NFSHelper(NASHelperBase):
] ]
self._ssh_exec(server, sync_cmd) self._ssh_exec(server, sync_cmd)
def get_share_path_by_export_location(self, server, export_location):
return export_location.split(':')[-1]
class CIFSHelper(NASHelperBase): class CIFSHelper(NASHelperBase):
"""Manage shares in samba server by net conf tool. """Manage shares in samba server by net conf tool.
@ -855,3 +969,21 @@ class CIFSHelper(NASHelperBase):
value = "\"" + ' '.join(hosts) + "\"" value = "\"" + ' '.join(hosts) + "\""
self._ssh_exec(server, ['sudo', 'net', 'conf', 'setparm', share_name, self._ssh_exec(server, ['sudo', 'net', 'conf', 'setparm', share_name,
'\"hosts allow\"', value]) '\"hosts allow\"', value])
def get_share_path_by_export_location(self, server, export_location):
# Get name of group that contains share data on CIFS server
if export_location.startswith('\\\\'):
group_name = export_location.split('\\')[-1]
elif export_location.startswith('//'):
group_name = export_location.split('/')[-1]
else:
msg = _("Got incorrect CIFS export location "
"'%s'.") % export_location
raise exception.InvalidShare(reason=msg)
# Get parameter 'path' from group that belongs to current share
(out, __) = self._ssh_exec(
server, ['sudo', 'net', 'conf', 'getparm', group_name, 'path'])
# Remove special symbols from response and return path
return out.strip()

View File

@ -340,8 +340,7 @@ class ShareManager(manager.SchedulerDependentManager):
raise exception.InvalidShare(reason=msg) raise exception.InvalidShare(reason=msg)
share_update = ( share_update = (
self.driver.manage_existing(share_ref, driver_options) or {} self.driver.manage_existing(share_ref, driver_options) or {})
)
if not share_update.get('size'): if not share_update.get('size'):
msg = _("Driver cannot calculate share size.") msg = _("Driver cannot calculate share size.")
@ -349,19 +348,22 @@ class ShareManager(manager.SchedulerDependentManager):
self._update_quota_usages(context, project_id, { self._update_quota_usages(context, project_id, {
"shares": 1, "shares": 1,
"gigabytes": share_update['size'] "gigabytes": share_update['size'],
}) })
share_update.update({ share_update.update({
'status': 'available', 'status': 'available',
'launched_at': timeutils.utcnow() 'launched_at': timeutils.utcnow(),
}) })
self.db.share_update(context, share_id, share_update) self.db.share_update(context, share_id, share_update)
except Exception as e: except Exception as e:
LOG.error(_LW("Manage share failed: %s"), six.text_type(e)) LOG.error(_LW("Manage share failed: %s"), six.text_type(e))
self.db.share_update(context, share_id, # NOTE(vponomaryov): set size as 1 because design expects size
{'status': constants.STATUS_MANAGE_ERROR}) # to be set, it also will allow us to handle delete/unmanage
# operations properly with this errored share according to quotas.
self.db.share_update(
context, share_id,
{'status': constants.STATUS_MANAGE_ERROR, 'size': 1})
def _update_quota_usages(self, context, project_id, usages): def _update_quota_usages(self, context, project_id, usages):
user_id = context.user_id user_id = context.user_id

View File

@ -141,12 +141,14 @@ class ShareManageTest(test.TestCase):
def test_share_manage(self): def test_share_manage(self):
body = get_fake_manage_body() body = get_fake_manage_body()
self._setup_manage_mocks() self._setup_manage_mocks()
self.mock_object(share_api.API, 'manage') share = {'share_type_id': '', 'id': 'fake'}
self.mock_object(share_api.API, 'manage',
mock.Mock(return_value=share))
share = { share = {
'host': body['share']['service_host'], 'host': body['share']['service_host'],
'export_location': body['share']['export_path'], 'export_location': body['share']['export_path'],
'share_proto': body['share']['protocol'], 'share_proto': body['share']['protocol'].upper(),
'share_type_id': 'fake', 'share_type_id': 'fake',
'display_name': body['share']['display_name'], 'display_name': body['share']['display_name'],
'display_description': body['share']['display_description'], 'display_description': body['share']['display_description'],
@ -156,7 +158,7 @@ class ShareManageTest(test.TestCase):
share_api.API.manage.assert_called_once_with( share_api.API.manage.assert_called_once_with(
mock.ANY, share, {}) mock.ANY, share, {})
self.assertEqual(202, actual_result.status_int) self.assertIsNotNone(actual_result)
def test_wrong_permissions(self): def test_wrong_permissions(self):
body = get_fake_manage_body() body = get_fake_manage_body()

View File

@ -29,6 +29,7 @@ from manila import context
from manila import exception from manila import exception
import manila.share.configuration import manila.share.configuration
from manila.share.drivers import generic from manila.share.drivers import generic
from manila.share import share_types
from manila import test from manila import test
from manila.tests import fake_compute from manila.tests import fake_compute
from manila.tests import fake_service_instance from manila.tests import fake_service_instance
@ -41,6 +42,16 @@ from manila import volume
CONF = cfg.CONF CONF = cfg.CONF
def get_fake_manage_share():
return {
'id': 'fake',
'share_proto': 'NFS',
'share_type_id': 'fake',
'export_locations': [
'10.0.0.1:/foo/fake/path', '11.0.0.1:/bar/fake/path'],
}
@ddt.ddt @ddt.ddt
class GenericShareDriverTestCase(test.TestCase): class GenericShareDriverTestCase(test.TestCase):
"""Tests GenericShareDriver.""" """Tests GenericShareDriver."""
@ -185,21 +196,18 @@ class GenericShareDriverTestCase(test.TestCase):
def test_mount_device_not_present(self): def test_mount_device_not_present(self):
server = {'instance_id': 'fake_server_id'} server = {'instance_id': 'fake_server_id'}
mount_path = '/fake/mount/path' mount_path = self._driver._get_mount_path(self.share)
volume = {'mountpoint': 'fake_mount_point'} volume = {'mountpoint': 'fake_mount_point'}
self.mock_object(self._driver, '_is_device_mounted', self.mock_object(self._driver, '_is_device_mounted',
mock.Mock(return_value=False)) mock.Mock(return_value=False))
self.mock_object(self._driver, '_sync_mount_temp_and_perm_files') self.mock_object(self._driver, '_sync_mount_temp_and_perm_files')
self.mock_object(self._driver, '_get_mount_path',
mock.Mock(return_value=mount_path))
self.mock_object(self._driver, '_ssh_exec', self.mock_object(self._driver, '_ssh_exec',
mock.Mock(return_value=('', ''))) mock.Mock(return_value=('', '')))
self._driver._mount_device(self.share, server, volume) self._driver._mount_device(self.share, server, volume)
self._driver._get_mount_path.assert_called_once_with(self.share)
self._driver._is_device_mounted.assert_called_once_with( self._driver._is_device_mounted.assert_called_once_with(
self.share, server, volume) mount_path, server, volume)
self._driver._sync_mount_temp_and_perm_files.assert_called_once_with( self._driver._sync_mount_temp_and_perm_files.assert_called_once_with(
server) server)
self._driver._ssh_exec.assert_called_once_with( self._driver._ssh_exec.assert_called_once_with(
@ -222,13 +230,12 @@ class GenericShareDriverTestCase(test.TestCase):
self._driver._get_mount_path.assert_called_once_with(self.share) self._driver._get_mount_path.assert_called_once_with(self.share)
self._driver._is_device_mounted.assert_called_once_with( self._driver._is_device_mounted.assert_called_once_with(
self.share, self.server, volume) mount_path, self.server, volume)
generic.LOG.warning.assert_called_once_with(mock.ANY, mock.ANY) generic.LOG.warning.assert_called_once_with(mock.ANY, mock.ANY)
def test_mount_device_exception_raised(self): def test_mount_device_exception_raised(self):
volume = {'mountpoint': 'fake_mount_point'} volume = {'mountpoint': 'fake_mount_point'}
self.mock_object(self._driver, '_get_mount_path',
mock.Mock(return_value='fake'))
self.mock_object( self.mock_object(
self._driver, '_is_device_mounted', self._driver, '_is_device_mounted',
mock.Mock(side_effect=exception.ProcessExecutionError)) mock.Mock(side_effect=exception.ProcessExecutionError))
@ -240,9 +247,8 @@ class GenericShareDriverTestCase(test.TestCase):
self.server, self.server,
volume, volume,
) )
self._driver._get_mount_path.assert_called_once_with(self.share)
self._driver._is_device_mounted.assert_called_once_with( self._driver._is_device_mounted.assert_called_once_with(
self.share, self.server, volume) self._driver._get_mount_path(self.share), self.server, volume)
def test_unmount_device_present(self): def test_unmount_device_present(self):
mount_path = '/fake/mount/path' mount_path = '/fake/mount/path'
@ -258,7 +264,7 @@ class GenericShareDriverTestCase(test.TestCase):
self._driver._get_mount_path.assert_called_once_with(self.share) self._driver._get_mount_path.assert_called_once_with(self.share)
self._driver._is_device_mounted.assert_called_once_with( self._driver._is_device_mounted.assert_called_once_with(
self.share, self.server) mount_path, self.server)
self._driver._sync_mount_temp_and_perm_files.assert_called_once_with( self._driver._sync_mount_temp_and_perm_files.assert_called_once_with(
self.server) self.server)
self._driver._ssh_exec.assert_called_once_with( self._driver._ssh_exec.assert_called_once_with(
@ -278,7 +284,7 @@ class GenericShareDriverTestCase(test.TestCase):
self._driver._get_mount_path.assert_called_once_with(self.share) self._driver._get_mount_path.assert_called_once_with(self.share)
self._driver._is_device_mounted.assert_called_once_with( self._driver._is_device_mounted.assert_called_once_with(
self.share, self.server) mount_path, self.server)
generic.LOG.warning.assert_called_once_with(mock.ANY, mock.ANY) generic.LOG.warning.assert_called_once_with(mock.ANY, mock.ANY)
def test_is_device_mounted_true(self): def test_is_device_mounted_true(self):
@ -288,13 +294,10 @@ class GenericShareDriverTestCase(test.TestCase):
'path': mount_path} 'path': mount_path}
self.mock_object(self._driver, '_ssh_exec', self.mock_object(self._driver, '_ssh_exec',
mock.Mock(return_value=(mounts, ''))) mock.Mock(return_value=(mounts, '')))
self.mock_object(self._driver, '_get_mount_path',
mock.Mock(return_value=mount_path))
result = self._driver._is_device_mounted( result = self._driver._is_device_mounted(
self.share, self.server, volume) mount_path, self.server, volume)
self._driver._get_mount_path.assert_called_once_with(self.share)
self._driver._ssh_exec.assert_called_once_with( self._driver._ssh_exec.assert_called_once_with(
self.server, ['sudo', 'mount']) self.server, ['sudo', 'mount'])
self.assertEqual(result, True) self.assertEqual(result, True)
@ -304,12 +307,9 @@ class GenericShareDriverTestCase(test.TestCase):
mounts = "/fake/dev/path on %(path)s type fake" % {'path': mount_path} mounts = "/fake/dev/path on %(path)s type fake" % {'path': mount_path}
self.mock_object(self._driver, '_ssh_exec', self.mock_object(self._driver, '_ssh_exec',
mock.Mock(return_value=(mounts, ''))) mock.Mock(return_value=(mounts, '')))
self.mock_object(self._driver, '_get_mount_path',
mock.Mock(return_value=mount_path))
result = self._driver._is_device_mounted(self.share, self.server) result = self._driver._is_device_mounted(mount_path, self.server)
self._driver._get_mount_path.assert_called_once_with(self.share)
self._driver._ssh_exec.assert_called_once_with( self._driver._ssh_exec.assert_called_once_with(
self.server, ['sudo', 'mount']) self.server, ['sudo', 'mount'])
self.assertEqual(result, True) self.assertEqual(result, True)
@ -321,13 +321,10 @@ class GenericShareDriverTestCase(test.TestCase):
'path': mount_path} 'path': mount_path}
self.mock_object(self._driver, '_ssh_exec', self.mock_object(self._driver, '_ssh_exec',
mock.Mock(return_value=(mounts, ''))) mock.Mock(return_value=(mounts, '')))
self.mock_object(self._driver, '_get_mount_path',
mock.Mock(return_value=mount_path))
result = self._driver._is_device_mounted( result = self._driver._is_device_mounted(
self.share, self.server, volume) mount_path, self.server, volume)
self._driver._get_mount_path.assert_called_once_with(self.share)
self._driver._ssh_exec.assert_called_once_with( self._driver._ssh_exec.assert_called_once_with(
self.server, ['sudo', 'mount']) self.server, ['sudo', 'mount'])
self.assertEqual(result, False) self.assertEqual(result, False)
@ -340,9 +337,8 @@ class GenericShareDriverTestCase(test.TestCase):
self.mock_object(self._driver, '_get_mount_path', self.mock_object(self._driver, '_get_mount_path',
mock.Mock(return_value=mount_path)) mock.Mock(return_value=mount_path))
result = self._driver._is_device_mounted(self.share, self.server) result = self._driver._is_device_mounted(mount_path, self.server)
self._driver._get_mount_path.assert_called_once_with(self.share)
self._driver._ssh_exec.assert_called_once_with( self._driver._ssh_exec.assert_called_once_with(
self.server, ['sudo', 'mount']) self.server, ['sudo', 'mount'])
self.assertEqual(result, False) self.assertEqual(result, False)
@ -467,8 +463,12 @@ class GenericShareDriverTestCase(test.TestCase):
self._driver.configuration.volume_name_template % self.share['id']) self._driver.configuration.volume_name_template % self.share['id'])
self.mock_object(self._driver.volume_api, 'get_all', self.mock_object(self._driver.volume_api, 'get_all',
mock.Mock(return_value=[])) mock.Mock(return_value=[]))
result = self._driver._get_volume(self._context, self.share['id'])
self.assertEqual(result, None) self.assertRaises(
exception.VolumeNotFound,
self._driver._get_volume,
self._context, self.share['id'])
self._driver.volume_api.get_all.assert_called_once_with( self._driver.volume_api.get_all.assert_called_once_with(
self._context, {'all_tenants': True, 'name': vol_name}) self._context, {'all_tenants': True, 'name': vol_name})
@ -1011,6 +1011,183 @@ class GenericShareDriverTestCase(test.TestCase):
self.assertEqual(True, result['driver_handles_share_servers']) self.assertEqual(True, result['driver_handles_share_servers'])
self.assertEqual('Open Source', result['vendor_name']) self.assertEqual('Open Source', result['vendor_name'])
def test_manage_invalid_driver_mode(self):
CONF.set_default('driver_handles_share_servers', True)
self.assertRaises(exception.InvalidDriverMode,
self._driver.manage_existing, 'fake', {})
def _setup_manage_mocks(self,
get_share_type_extra_specs='False',
is_device_mounted=True,
server_details=None):
CONF.set_default('driver_handles_share_servers', False)
self.mock_object(share_types, 'get_share_type_extra_specs',
mock.Mock(return_value=get_share_type_extra_specs))
self.mock_object(self._driver, '_is_device_mounted',
mock.Mock(return_value=is_device_mounted))
self.mock_object(self._driver, 'service_instance_manager')
server = {'backend_details': server_details}
self.mock_object(self._driver.service_instance_manager,
'get_common_server',
mock.Mock(return_value=server))
def test_manage_invalid_protocol(self):
share = {'share_proto': 'fake_proto'}
self._setup_manage_mocks()
self.assertRaises(exception.InvalidShare,
self._driver.manage_existing, share, {})
def test_manage_share_type_mismatch(self):
share = {'share_proto': 'NFS', 'share_type_id': 'fake'}
self._setup_manage_mocks(get_share_type_extra_specs='True')
self.assertRaises(exception.ManageExistingShareTypeMismatch,
self._driver.manage_existing, share, {})
share_types.get_share_type_extra_specs.assert_called_once_with(
share['share_type_id'],
const.ExtraSpecs.DRIVER_HANDLES_SHARE_SERVERS
)
def test_manage_not_mounted_share(self):
share = get_fake_manage_share()
fake_path = '/foo/bar'
self._setup_manage_mocks(is_device_mounted=False)
self.mock_object(
self._driver._helpers[share['share_proto']],
'get_share_path_by_export_location',
mock.Mock(return_value=fake_path))
self.assertRaises(exception.ManageInvalidShare,
self._driver.manage_existing, share, {})
self.assertEqual(
1,
self._driver.service_instance_manager.get_common_server.call_count)
self._driver._is_device_mounted.assert_called_once_with(
fake_path, None)
self._driver._helpers[share['share_proto']].\
get_share_path_by_export_location.assert_called_once_with(
None, share['export_locations'][0])
def test_manage_share_not_attached_to_cinder_volume_invalid_size(self):
share = get_fake_manage_share()
server_details = {}
fake_path = '/foo/bar'
self._setup_manage_mocks(server_details=server_details)
self.mock_object(self._driver, '_get_volume',
mock.Mock(return_value=None))
error = exception.ManageInvalidShare(reason="fake")
self.mock_object(
self._driver, '_get_mounted_share_size',
mock.Mock(side_effect=error))
self.mock_object(
self._driver._helpers[share['share_proto']],
'get_share_path_by_export_location',
mock.Mock(return_value=fake_path))
self.assertRaises(exception.ManageInvalidShare,
self._driver.manage_existing, share, {})
self._driver._get_mounted_share_size.assert_called_once_with(
fake_path, server_details)
self._driver._helpers[share['share_proto']].\
get_share_path_by_export_location.assert_called_once_with(
server_details, share['export_locations'][0])
def test_manage_share_not_attached_to_cinder_volume(self):
share = get_fake_manage_share()
share_size = "fake"
server_details = {}
self._setup_manage_mocks(server_details=server_details)
self.mock_object(self._driver, '_get_volume',
mock.Mock(return_value=None))
self.mock_object(self._driver, '_get_mounted_share_size',
mock.Mock(return_value=share_size))
actual_result = self._driver.manage_existing(share, {})
self.assertEqual({'size': share_size}, actual_result)
def test_manage_share_attached_to_cinder_volume_not_found(self):
share = get_fake_manage_share()
server_details = {}
driver_options = {'volume_id': 'fake'}
self._setup_manage_mocks(server_details=server_details)
self.mock_object(
self._driver.volume_api, 'get',
mock.Mock(side_effect=exception.VolumeNotFound(volume_id="fake"))
)
self.assertRaises(exception.ManageInvalidShare,
self._driver.manage_existing, share, driver_options)
self._driver.volume_api.get.assert_called_once_with(
mock.ANY, driver_options['volume_id'])
def test_manage_share_attached_to_cinder_volume_not_mounted_to_srv(self):
share = get_fake_manage_share()
server_details = {'instance_id': 'fake'}
driver_options = {'volume_id': 'fake'}
volume = {'id': 'fake'}
self._setup_manage_mocks(server_details=server_details)
self.mock_object(self._driver.volume_api, 'get',
mock.Mock(return_value=volume))
self.mock_object(self._driver.compute_api, 'instance_volumes_list',
mock.Mock(return_value=[]))
self.assertRaises(exception.ManageInvalidShare,
self._driver.manage_existing, share, driver_options)
self._driver.volume_api.get.assert_called_once_with(
mock.ANY, driver_options['volume_id'])
self._driver.compute_api.instance_volumes_list.assert_called_once_with(
mock.ANY, server_details['instance_id'])
def test_manage_share_attached_to_cinder_volume(self):
share = get_fake_manage_share()
server_details = {'instance_id': 'fake'}
driver_options = {'volume_id': 'fake'}
volume = {'id': 'fake', 'name': 'fake_volume_1', 'size': 'fake'}
self._setup_manage_mocks(server_details=server_details)
self.mock_object(self._driver.volume_api, 'get',
mock.Mock(return_value=volume))
self._driver.volume_api.update = mock.Mock()
fake_volume = mock.Mock()
fake_volume.id = 'fake'
self.mock_object(self._driver.compute_api, 'instance_volumes_list',
mock.Mock(return_value=[fake_volume]))
actual_result = self._driver.manage_existing(share, driver_options)
self.assertEqual({'size': 'fake'}, actual_result)
expected_volume_update = {
'name': self._driver._get_volume_name(share['id'])
}
self._driver.volume_api.update.assert_called_once_with(
mock.ANY, volume['id'], expected_volume_update)
def test_get_mounted_share_size(self):
output = ("Filesystem blocks Used Available Capacity Mounted on\n"
"/dev/fake 1G 1G 1G 4% /shares/share-fake")
self.mock_object(self._driver, '_ssh_exec',
mock.Mock(return_value=(output, '')))
actual_result = self._driver._get_mounted_share_size('/fake/path', {})
self.assertEqual(1, actual_result)
@ddt.data("fake\nfake\n", "fake", "fake\n")
def test_get_mounted_share_size_invalid_output(self, output):
self.mock_object(self._driver, '_ssh_exec',
mock.Mock(return_value=(output, '')))
self.assertRaises(exception.ManageInvalidShare,
self._driver._get_mounted_share_size,
'/fake/path', {})
@generic.ensure_server @generic.ensure_server
def fake(driver_instance, context, share_server=None): def fake(driver_instance, context, share_server=None):

View File

@ -550,6 +550,9 @@ class ShareAPITestCase(test.TestCase):
status='creating') status='creating')
self.mock_object(db_driver, 'share_create', self.mock_object(db_driver, 'share_create',
mock.Mock(return_value=share)) mock.Mock(return_value=share))
self.mock_object(db_driver, 'share_export_locations_update')
self.mock_object(db_driver, 'share_get',
mock.Mock(return_value=share))
self.mock_object(self.api, 'get_all', mock.Mock(return_value=[])) self.mock_object(self.api, 'get_all', mock.Mock(return_value=[]))
self.api.manage(self.context, self.api.manage(self.context,
@ -561,17 +564,20 @@ class ShareAPITestCase(test.TestCase):
'project_id': self.context.project_id, 'project_id': self.context.project_id,
'status': constants.STATUS_MANAGING, 'status': constants.STATUS_MANAGING,
'scheduled_at': date, 'scheduled_at': date,
'deleted': False,
}) })
export_location = share_data.pop('export_location')
self.api.get_all.assert_called_once_with(self.context, mock.ANY) self.api.get_all.assert_called_once_with(self.context, mock.ANY)
db_driver.share_create.assert_called_once_with(self.context, db_driver.share_create.assert_called_once_with(self.context,
share_data) share_data)
db_driver.share_get.assert_called_once_with(self.context, share['id'])
db_driver.share_export_locations_update.assert_called_once_with(
self.context, share['id'], export_location
)
self.share_rpcapi.manage_share.assert_called_once_with( self.share_rpcapi.manage_share.assert_called_once_with(
self.context, share, driver_options) self.context, share, driver_options)
@ddt.data([{'id': 'fake', 'status': constants.STATUS_MANAGE_ERROR}], @ddt.data([{'id': 'fake', 'status': constants.STATUS_MANAGE_ERROR}])
[{'id': 'fake', 'status': constants.STATUS_UNMANAGED}],)
def test_manage_retry(self, shares): def test_manage_retry(self, shares):
share_data = { share_data = {
'host': 'fake', 'host': 'fake',
@ -579,8 +585,12 @@ class ShareAPITestCase(test.TestCase):
'share_proto': 'fake', 'share_proto': 'fake',
} }
driver_options = {} driver_options = {}
share = fake_share('fakeid')
self.mock_object(db_driver, 'share_update', self.mock_object(db_driver, 'share_update',
mock.Mock(return_value=fake_share('fakeid'))) mock.Mock(return_value=share))
self.mock_object(db_driver, 'share_get',
mock.Mock(return_value=share))
self.mock_object(db_driver, 'share_export_locations_update')
self.mock_object(self.api, 'get_all', self.mock_object(self.api, 'get_all',
mock.Mock(return_value=shares)) mock.Mock(return_value=shares))
@ -592,6 +602,9 @@ class ShareAPITestCase(test.TestCase):
self.context, 'fake', mock.ANY) self.context, 'fake', mock.ANY)
self.share_rpcapi.manage_share.assert_called_once_with( self.share_rpcapi.manage_share.assert_called_once_with(
self.context, mock.ANY, driver_options) self.context, mock.ANY, driver_options)
db_driver.share_export_locations_update.assert_called_once_with(
self.context, share['id'], mock.ANY
)
def test_manage_duplicate(self): def test_manage_duplicate(self):
share_data = { share_data = {

View File

@ -710,8 +710,8 @@ class ShareManagerTestCase(test.TestCase):
self.share_manager.manage_share(self.context, share_id, {}) self.share_manager.manage_share(self.context, share_id, {})
self.share_manager.db.share_update.assert_called_once_with( self.share_manager.db.share_update.assert_called_once_with(
mock.ANY, share_id, {'status': constants.STATUS_MANAGE_ERROR} mock.ANY, share_id,
) {'status': constants.STATUS_MANAGE_ERROR, 'size': 1})
def test_manage_share_driver_exception(self): def test_manage_share_driver_exception(self):
self.mock_object(self.share_manager, 'driver', mock.Mock()) self.mock_object(self.share_manager, 'driver', mock.Mock())
@ -729,8 +729,8 @@ class ShareManagerTestCase(test.TestCase):
assert_called_once_with(mock.ANY, driver_options) assert_called_once_with(mock.ANY, driver_options)
self.share_manager.db.share_update.assert_called_once_with( self.share_manager.db.share_update.assert_called_once_with(
mock.ANY, share_id, {'status': constants.STATUS_MANAGE_ERROR} mock.ANY, share_id,
) {'status': constants.STATUS_MANAGE_ERROR, 'size': 1})
def test_manage_share_invalid_size(self): def test_manage_share_invalid_size(self):
self.mock_object(self.share_manager, 'driver') self.mock_object(self.share_manager, 'driver')
@ -749,8 +749,8 @@ class ShareManagerTestCase(test.TestCase):
assert_called_once_with(mock.ANY, driver_options) assert_called_once_with(mock.ANY, driver_options)
self.share_manager.db.share_update.assert_called_once_with( self.share_manager.db.share_update.assert_called_once_with(
mock.ANY, share_id, {'status': constants.STATUS_MANAGE_ERROR} mock.ANY, share_id,
) {'status': constants.STATUS_MANAGE_ERROR, 'size': 1})
def test_manage_share_quota_error(self): def test_manage_share_quota_error(self):
self.mock_object(self.share_manager, 'driver') self.mock_object(self.share_manager, 'driver')
@ -771,8 +771,8 @@ class ShareManagerTestCase(test.TestCase):
assert_called_once_with(mock.ANY, driver_options) assert_called_once_with(mock.ANY, driver_options)
self.share_manager.db.share_update.assert_called_once_with( self.share_manager.db.share_update.assert_called_once_with(
mock.ANY, share_id, {'status': constants.STATUS_MANAGE_ERROR} mock.ANY, share_id,
) {'status': constants.STATUS_MANAGE_ERROR, 'size': 1})
@ddt.data({'size': 1}, @ddt.data({'size': 1},
{'size': 1, 'name': 'fake'}) {'size': 1, 'name': 'fake'})

View File

@ -129,8 +129,18 @@ class CinderApiTestCase(test.TestCase):
self.assertIsNone(self.api.check_detach(self.ctx, volume)) self.assertIsNone(self.api.check_detach(self.ctx, volume))
def test_update(self): def test_update(self):
self.assertRaises(NotImplementedError, fake_volume = {'fake': 'fake'}
self.api.update, self.ctx, '', '') self.mock_object(self.cinderclient.volumes, 'get',
mock.Mock(return_value=fake_volume))
self.mock_object(self.cinderclient.volumes, 'update')
fake_volume_id = 'fake_volume'
fake_data = {'test': 'test'}
self.api.update(self.ctx, fake_volume_id, fake_data)
self.cinderclient.volumes.get.assert_called_once_with(fake_volume_id)
self.cinderclient.volumes.update.assert_called_once_with(fake_volume,
**fake_data)
def test_reserve_volume(self): def test_reserve_volume(self):
self.mock_object(self.cinderclient.volumes, 'reserve') self.mock_object(self.cinderclient.volumes, 'reserve')

View File

@ -26,6 +26,7 @@ from cinderclient.v2 import client as cinder_client
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log from oslo_log import log
import manila.context as ctxt
from manila.db import base from manila.db import base
from manila import exception from manila import exception
from manila.i18n import _ from manila.i18n import _
@ -317,7 +318,12 @@ class API(base.Base):
@translate_volume_exception @translate_volume_exception
def update(self, context, volume_id, fields): def update(self, context, volume_id, fields):
raise NotImplementedError() # Use Manila's context as far as Cinder's is restricted to update
# volumes.
manila_admin_context = ctxt.get_admin_context()
client = cinderclient(manila_admin_context)
item = client.volumes.get(volume_id)
client.volumes.update(item, **fields)
def get_volume_encryption_metadata(self, context, volume_id): def get_volume_encryption_metadata(self, context, volume_id):
return cinderclient(context).volumes.get_encryption_metadata(volume_id) return cinderclient(context).volumes.get_encryption_metadata(volume_id)