Add Replication admin APIs and driver i/f changes

Add admin-only /share-replicas/replica-id/action APIs:

+---------------------+----------------------------------------+
| reset_state         | To reset the 'status' attribute of     |
|                     |  a replica                             |
+---------------------+----------------------------------------+
| reset_replica_state | To reset the 'replica_state' attribute |
|                     |  of a replica.                         |
+---------------------+----------------------------------------+
| force_delete        | To delete from the DB and attempt      |
|                     | to remove it from the backend.         |
+---------------------+----------------------------------------+
| resync              | To attempt update of a replica         |
+---------------------+----------------------------------------+

Modify the driver interface for create_replica, delete_replica
and update_replica to hand down more information to the driver
during replica operations.

Implements bp: share-replication-admin-actions

APIImpact
DocImpact

Change-Id: I1352f112f3627af4424eb2e3be428b1d13c1d2d5
This commit is contained in:
Goutham Pacha Ravi 2016-02-16 20:59:27 -05:00
parent 68925cbac7
commit c288d9020a
15 changed files with 728 additions and 228 deletions

View File

@ -116,5 +116,9 @@
"share_replica:show": "rule:default",
"share_replica:create" : "rule:default",
"share_replica:delete": "rule:default",
"share_replica:promote": "rule:default"
"share_replica:promote": "rule:default",
"share_replica:resync": "rule:admin_api",
"share_replica:reset_status": "rule:admin_api",
"share_replica:force_delete": "rule:admin_api",
"share_replica:reset_replica_state": "rule:admin_api"
}

View File

@ -1174,13 +1174,26 @@ class Controller(object):
class AdminActionsMixin(object):
"""Mixin class for API controllers with admin actions."""
valid_statuses = set([
constants.STATUS_CREATING,
constants.STATUS_AVAILABLE,
constants.STATUS_DELETING,
constants.STATUS_ERROR,
constants.STATUS_ERROR_DELETING,
])
body_attributes = {
'status': 'reset_status',
'replica_state': 'reset_replica_state',
}
valid_statuses = {
'status': set([
constants.STATUS_CREATING,
constants.STATUS_AVAILABLE,
constants.STATUS_DELETING,
constants.STATUS_ERROR,
constants.STATUS_ERROR_DELETING,
]),
'replica_state': set([
constants.REPLICA_STATE_ACTIVE,
constants.REPLICA_STATE_IN_SYNC,
constants.REPLICA_STATE_OUT_OF_SYNC,
constants.STATUS_ERROR,
]),
}
def _update(self, *args, **kwargs):
raise NotImplementedError()
@ -1191,24 +1204,27 @@ class AdminActionsMixin(object):
def _delete(self, *args, **kwargs):
raise NotImplementedError()
def validate_update(self, body):
def validate_update(self, body, status_attr='status'):
update = {}
try:
update['status'] = body['status']
update[status_attr] = body[status_attr]
except (TypeError, KeyError):
raise webob.exc.HTTPBadRequest(explanation="Must specify 'status'")
if update['status'] not in self.valid_statuses:
msg = _("Must specify '%s'") % status_attr
raise webob.exc.HTTPBadRequest(explanation=msg)
if update[status_attr] not in self.valid_statuses[status_attr]:
expl = (_("Invalid state. Valid states: %s.") %
", ".join(self.valid_statuses))
", ".join(self.valid_statuses[status_attr]))
raise webob.exc.HTTPBadRequest(explanation=expl)
return update
@Controller.authorize('reset_status')
def _reset_status(self, req, id, body):
"""Reset status on the resource."""
def _reset_status(self, req, id, body, status_attr='status'):
"""Reset the status_attr specified on the resource."""
context = req.environ['manila.context']
body_attr = self.body_attributes[status_attr]
update = self.validate_update(
body.get('reset_status', body.get('os-reset_status')))
body.get(body_attr, body.get('-'.join(('os', body_attr)))),
status_attr=status_attr)
msg = "Updating %(resource)s '%(id)s' with '%(update)r'"
LOG.debug(msg, {'resource': self.resource_name, 'id': id,
'update': update})

View File

@ -34,7 +34,7 @@ LOG = log.getLogger(__name__)
MIN_SUPPORTED_API_VERSION = '2.11'
class ShareReplicationController(wsgi.Controller):
class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin):
"""The Share Replication API controller for the OpenStack API."""
resource_name = 'share_replica'
@ -44,6 +44,18 @@ class ShareReplicationController(wsgi.Controller):
super(ShareReplicationController, self).__init__()
self.share_api = share.API()
def _update(self, *args, **kwargs):
db.share_replica_update(*args, **kwargs)
def _get(self, *args, **kwargs):
return db.share_replica_get(*args, **kwargs)
def _delete(self, context, resource, force=True):
try:
self.share_api.delete_share_replica(context, resource, force=True)
except exception.ReplicationException as e:
raise exc.HTTPBadRequest(explanation=six.text_type(e))
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
def index(self, req):
"""Return a summary list of replicas."""
@ -177,6 +189,48 @@ class ShareReplicationController(wsgi.Controller):
return self._view_builder.detail(req, replica)
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
@wsgi.action('reset_status')
def reset_status(self, req, id, body):
"""Reset the 'status' attribute in the database."""
return self._reset_status(req, id, body)
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
@wsgi.action('force_delete')
def force_delete(self, req, id, body):
"""Force deletion on the database, attempt on the backend."""
return self._force_delete(req, id, body)
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
@wsgi.action('reset_replica_state')
@wsgi.Controller.authorize
def reset_replica_state(self, req, id, body):
"""Reset the 'replica_state' attribute in the database."""
return self._reset_status(req, id, body, status_attr='replica_state')
@wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True)
@wsgi.action('resync')
@wsgi.response(202)
@wsgi.Controller.authorize
def resync(self, req, id, body):
"""Attempt to update/sync the replica with its source."""
context = req.environ['manila.context']
try:
replica = db.share_replica_get(context, id)
except exception.ShareReplicaNotFound:
msg = _("No replica exists with ID %s.")
raise exc.HTTPNotFound(explanation=msg % id)
replica_state = replica.get('replica_state')
if replica_state == constants.REPLICA_STATE_ACTIVE:
return webob.Response(status_int=200)
try:
self.share_api.update_share_replica(context, replica)
except exception.InvalidHost as e:
raise exc.HTTPBadRequest(explanation=six.text_type(e))
def create_resource():
return wsgi.Resource(ShareReplicationController())

View File

@ -400,7 +400,8 @@ class API(base.Base):
return share_replica
def delete_share_replica(self, context, share_replica, force=False):
# Disallow deletion of ONLY active replica
# Disallow deletion of ONLY active replica, *even* when this
# operation is forced.
replicas = self.db.share_replicas_get_all_by_share(
context, share_replica['share_id'])
active_replicas = list(filter(
@ -418,20 +419,15 @@ class API(base.Base):
{'terminated_at': timeutils.utcnow()})
self.db.share_replica_delete(context, share_replica['id'])
else:
host = share_utils.extract_host(share_replica['host'])
self.db.share_replica_update(
context, share_replica['id'],
{'status': constants.STATUS_DELETING,
'terminated_at': timeutils.utcnow()}
)
self.share_rpcapi.delete_share_replica(
context,
share_replica['id'],
host,
share_id=share_replica['share_id'],
force=force)
self.share_rpcapi.delete_share_replica(context,
share_replica,
force=force)
def promote_share_replica(self, context, share_replica):
@ -452,18 +448,22 @@ class API(base.Base):
raise exception.AdminRequired(
message=msg % replica_state)
host = share_utils.extract_host(share_replica['host'])
self.db.share_replica_update(
context, share_replica['id'],
{'status': constants.STATUS_REPLICATION_CHANGE})
self.share_rpcapi.promote_share_replica(
context, share_replica['id'], host,
share_id=share_replica['share_id'])
self.share_rpcapi.promote_share_replica(context, share_replica)
return self.db.share_replica_get(context, share_replica['id'])
def update_share_replica(self, context, share_replica):
if not share_replica['host']:
msg = _("Share replica does not have a valid host.")
raise exception.InvalidHost(reason=msg)
self.share_rpcapi.update_share_replica(context, share_replica)
def manage(self, context, share_data, driver_options):
policy.check_policy(context, 'share', 'manage')

View File

@ -1171,34 +1171,44 @@ class ShareDriver(object):
"""
return share_instances
def create_replica(self, context, active_replica, new_replica,
def create_replica(self, context, replica_list, new_replica,
access_rules, share_server=None):
"""Replicate the active replica to a new replica on this backend.
:param context: Current context
:param active_replica: A current active replica instance dictionary.
:param replica_list: List of all replicas for a particular share.
This list also contains the replica to be created. The 'active'
replica will have its 'replica_state' attr set to 'active'.
EXAMPLE:
.. code::
{
'id': 'd487b88d-e428-4230-a465-a800c2cce5f8',
'share_id': 'f0e4bb5e-65f0-11e5-9d70-feff819cdc9f',
'deleted': False,
'host': 'openstack2@cmodeSSVMNFS1',
'status': 'available',
'scheduled_at': datetime.datetime(2015, 8, 10, 0, 5, 58),
'launched_at': datetime.datetime(2015, 8, 10, 0, 5, 58),
'terminated_at': None,
'replica_state': 'active',
'availability_zone_id': 'e2c2db5c-cb2f-4697-9966-c06fb200cb80',
'export_locations': [
<models.ShareInstanceExportLocations>,
],
'access_rules_status': 'in_sync',
'share_network_id': '4ccd5318-65f1-11e5-9d70-feff819cdc9f',
'share_server_id': '4ce78e7b-0ef6-4730-ac2a-fd2defefbd05',
'share_server': <models.ShareServer> or None,
}
[
{
'id': 'd487b88d-e428-4230-a465-a800c2cce5f8',
'share_id': 'f0e4bb5e-65f0-11e5-9d70-feff819cdc9f',
'replica_state': 'in_sync',
...
'share_server_id': '4ce78e7b-0ef6-4730-ac2a-fd2defefbd05',
'share_server': <models.ShareServer> or None,
},
{
'id': '10e49c3e-aca9-483b-8c2d-1c337b38d6af',
'share_id': 'f0e4bb5e-65f0-11e5-9d70-feff819cdc9f',
'replica_state': 'active',
...
'share_server_id': 'f63629b3-e126-4448-bec2-03f788f76094',
'share_server': <models.ShareServer> or None,
},
{
'id': 'e82ff8b6-65f0-11e5-9d70-feff819cdc9f',
'share_id': 'f0e4bb5e-65f0-11e5-9d70-feff819cdc9f',
'replica_state': 'in_sync',
...
'share_server_id': '07574742-67ea-4dfd-9844-9fbd8ada3d87',
'share_server': <models.ShareServer> or None,
},
...
]
:param new_replica: The share replica dictionary.
EXAMPLE:
.. code::
@ -1259,34 +1269,44 @@ class ShareDriver(object):
"""
raise NotImplementedError()
def delete_replica(self, context, active_replica, replica,
def delete_replica(self, context, replica_list, replica,
share_server=None):
"""Delete a replica. This is called on the destination backend.
:param context: Current context
:param active_replica: A current active replica instance dictionary.
:param replica_list: List of all replicas for a particular share.
This list also contains the replica to be deleted. The 'active'
replica will have its 'replica_state' attr set to 'active'.
EXAMPLE:
.. code::
{
'id': 'd487b88d-e428-4230-a465-a800c2cce5f8',
'share_id': 'f0e4bb5e-65f0-11e5-9d70-feff819cdc9f',
'deleted': False,
'host': 'openstack2@cmodeSSVMNFS1',
'status': 'available',
'scheduled_at': datetime.datetime(2015, 8, 10, 0, 5, 58),
'launched_at': datetime.datetime(2015, 8, 10, 0, 5, 58),
'terminated_at': None,
'replica_state': 'active',
'availability_zone_id': 'e2c2db5c-cb2f-4697-9966-c06fb200cb80',
'export_locations': [
models.ShareInstanceExportLocations,
],
'access_rules_status': 'in_sync',
'share_network_id': '4ccd5318-65f1-11e5-9d70-feff819cdc9f',
'share_server_id': '4ce78e7b-0ef6-4730-ac2a-fd2defefbd05',
'share_server': <models.ShareServer> or None,
}
[
{
'id': 'd487b88d-e428-4230-a465-a800c2cce5f8',
'share_id': 'f0e4bb5e-65f0-11e5-9d70-feff819cdc9f',
'replica_state': 'in_sync',
...
'share_server_id': '4ce78e7b-0ef6-4730-ac2a-fd2defefbd05',
'share_server': <models.ShareServer> or None,
},
{
'id': '10e49c3e-aca9-483b-8c2d-1c337b38d6af',
'share_id': 'f0e4bb5e-65f0-11e5-9d70-feff819cdc9f',
'replica_state': 'active',
...
'share_server_id': 'f63629b3-e126-4448-bec2-03f788f76094',
'share_server': <models.ShareServer> or None,
},
{
'id': 'e82ff8b6-65f0-11e5-9d70-feff819cdc9f',
'share_id': 'f0e4bb5e-65f0-11e5-9d70-feff819cdc9f',
'replica_state': 'in_sync',
...
'share_server_id': '07574742-67ea-4dfd-9844-9fbd8ada3d87',
'share_server': <models.ShareServer> or None,
},
...
]
:param replica: Dictionary of the share replica being deleted.
EXAMPLE:
.. code::
@ -1407,14 +1427,50 @@ class ShareDriver(object):
"""
raise NotImplementedError()
def update_replica_state(self, context, replica,
def update_replica_state(self, context, replica_list, replica,
access_rules, share_server=None):
"""Update the replica_state of a replica.
Drivers should fix replication relationships that were broken if
possible inside this method.
This method is called periodically by the share manager; and
whenever requested by the administrator through the 'resync' API.
:param context: Current context
:param replica_list: List of all replicas for a particular share.
This list also contains the replica to be updated. The 'active'
replica will have its 'replica_state' attr set to 'active'.
EXAMPLE:
.. code::
[
{
'id': 'd487b88d-e428-4230-a465-a800c2cce5f8',
'share_id': 'f0e4bb5e-65f0-11e5-9d70-feff819cdc9f',
'replica_state': 'in_sync',
...
'share_server_id': '4ce78e7b-0ef6-4730-ac2a-fd2defefbd05',
'share_server': <models.ShareServer> or None,
},
{
'id': '10e49c3e-aca9-483b-8c2d-1c337b38d6af',
'share_id': 'f0e4bb5e-65f0-11e5-9d70-feff819cdc9f',
'replica_state': 'active',
...
'share_server_id': 'f63629b3-e126-4448-bec2-03f788f76094',
'share_server': <models.ShareServer> or None,
},
{
'id': 'e82ff8b6-65f0-11e5-9d70-feff819cdc9f',
'share_id': 'f0e4bb5e-65f0-11e5-9d70-feff819cdc9f',
'replica_state': 'in_sync',
...
'share_server_id': '07574742-67ea-4dfd-9844-9fbd8ada3d87',
'share_server': <models.ShareServer> or None,
},
...
]
:param replica: Dictionary of the replica being updated.
Replica state will always be 'in_sync', 'out_of_sync', or 'error'.
Replicas in 'active' state will not be passed via this parameter.

View File

@ -879,18 +879,18 @@ class ShareManager(manager.SchedulerDependentManager):
with_share_data=True
)
current_active_replica = (
_active_replica = (
self.db.share_replicas_get_available_active_replica(
context, share_replica['share_id'], with_share_data=True,
with_share_server=True))
if not current_active_replica:
if not _active_replica:
self.db.share_replica_update(
context, share_replica['id'],
{'status': constants.STATUS_ERROR,
'replica_state': constants.STATUS_ERROR})
msg = _("An active instance with 'available' status does "
"not exist to add replica to share %s.")
msg = _("An 'active' replica must exist in 'available' "
"state to create a new replica for share %s.")
raise exception.ReplicationException(
reason=msg % share_replica['share_id'])
@ -930,13 +930,19 @@ class ShareManager(manager.SchedulerDependentManager):
share_access_rules = self.db.share_instance_access_copy(
context, share_replica['share_id'], share_replica['id'])
current_active_replica = self._get_share_replica_dict(
context, current_active_replica)
replica_list = (
self.db.share_replicas_get_all_by_share(
context, share_replica['share_id'],
with_share_data=True, with_share_server=True)
)
replica_list = [self._get_share_replica_dict(context, r)
for r in replica_list]
share_replica = self._get_share_replica_dict(context, share_replica)
try:
replica_ref = self.driver.create_replica(
context, current_active_replica, share_replica,
context, replica_list, share_replica,
share_access_rules, share_server=share_server)
except Exception:
@ -988,16 +994,15 @@ class ShareManager(manager.SchedulerDependentManager):
context, share_replica_id, with_share_data=True,
with_share_server=True)
# Get the active replica
current_active_replica = (
self.db.share_replicas_get_available_active_replica(
replica_list = (
self.db.share_replicas_get_all_by_share(
context, share_replica['share_id'],
with_share_data=True, with_share_server=True)
)
share_server = self._get_share_server(context, share_replica)
current_active_replica = self._get_share_replica_dict(
context, current_active_replica)
replica_list = [self._get_share_replica_dict(context, r)
for r in replica_list]
share_server = self._get_share_server(context, share_replica)
share_replica = self._get_share_replica_dict(context, share_replica)
try:
@ -1023,7 +1028,7 @@ class ShareManager(manager.SchedulerDependentManager):
try:
self.driver.delete_replica(
context, current_active_replica, share_replica,
context, replica_list, share_replica,
share_server=share_server)
except Exception:
with excutils.save_and_reraise_exception() as exc_context:
@ -1121,23 +1126,26 @@ class ShareManager(manager.SchedulerDependentManager):
for updated_replica in updated_replica_list:
updated_export_locs = updated_replica.get(
'export_locations')
if(updated_export_locs and
isinstance(updated_export_locs, list)):
if(updated_export_locs is not None
and isinstance(updated_export_locs, list)):
self.db.share_export_locations_update(
context, updated_replica['id'],
updated_export_locs)
updated_replica_state = updated_replica.get(
'replica_state')
updates = {'replica_state': updated_replica_state}
updates = {}
# Change the promoted replica's status from 'available' to
# 'replication_change'.
if updated_replica['id'] == share_replica['id']:
updates['status'] = constants.STATUS_AVAILABLE
if updated_replica_state == constants.STATUS_ERROR:
updates['status'] = constants.STATUS_ERROR
self.db.share_replica_update(
context, updated_replica['id'], updates)
if updated_replica_state is not None:
updates['replica_state'] = updated_replica_state
if updates:
self.db.share_replica_update(
context, updated_replica['id'], updates)
if updated_replica.get('access_rules_status'):
self._update_share_replica_access_rules_state(
@ -1164,15 +1172,28 @@ class ShareManager(manager.SchedulerDependentManager):
self._share_replica_update(
context, replica, share_id=replica['share_id'])
@add_hooks
@utils.require_driver_initialized
def update_share_replica(self, context, share_replica_id, share_id=None):
"""Initiated by the force_update API."""
share_replica = self.db.share_replica_get(
context, share_replica_id, with_share_data=True,
with_share_server=True)
self._share_replica_update(context, share_replica, share_id=share_id)
@locked_share_replica_operation
def _share_replica_update(self, context, share_replica, share_id=None):
share_server = self._get_share_server(context, share_replica)
replica_state = None
# Re-grab the replica:
share_replica = self.db.share_replica_get(
context, share_replica['id'], with_share_data=True,
with_share_server=True)
try:
share_replica = self.db.share_replica_get(
context, share_replica['id'], with_share_data=True,
with_share_server=True)
except exception.ShareReplicaNotFound:
# Replica may have been deleted, nothing to do here
return
# We don't poll for replicas that are busy in some operation,
# or if they are the 'active' instance.
@ -1187,12 +1208,22 @@ class ShareManager(manager.SchedulerDependentManager):
LOG.debug("Updating status of share share_replica %s: ",
share_replica['id'])
replica_list = (
self.db.share_replicas_get_all_by_share(
context, share_replica['share_id'],
with_share_data=True, with_share_server=True)
)
replica_list = [self._get_share_replica_dict(context, r)
for r in replica_list]
share_replica = self._get_share_replica_dict(context, share_replica)
try:
replica_state = self.driver.update_replica_state(
context, share_replica, access_rules, share_server)
context, replica_list, share_replica, access_rules,
share_server=share_server)
except Exception:
# If the replica_state was previously in 'error', it is
@ -2129,7 +2160,7 @@ class ShareManager(manager.SchedulerDependentManager):
'status': share_replica.get('status'),
'replica_state': share_replica.get('replica_state'),
'availability_zone_id': share_replica.get('availability_zone_id'),
'export_locations': share_replica.get('export_locations'),
'export_locations': share_replica.get('export_locations') or [],
'share_network_id': share_replica.get('share_network_id'),
'share_server_id': share_replica.get('share_server_id'),
'deleted': share_replica.get('deleted'),

View File

@ -50,6 +50,7 @@ class ShareAPI(object):
create_share_replica()
delete_share_replica()
promote_share_replica()
update_share_replica()
"""
BASE_RPC_API_VERSION = '1.0'
@ -226,21 +227,27 @@ class ShareAPI(object):
filter_properties=filter_properties,
share_id=share_replica['share_id'])
def delete_share_replica(self, context, share_replica_id, host,
share_id=None, force=False):
new_host = utils.extract_host(host)
call_context = self.client.prepare(server=new_host, version='1.8')
def delete_share_replica(self, context, share_replica, force=False):
host = utils.extract_host(share_replica['host'])
call_context = self.client.prepare(server=host, version='1.8')
call_context.cast(context,
'delete_share_replica',
share_replica_id=share_replica_id,
share_id=share_id,
share_replica_id=share_replica['id'],
share_id=share_replica['share_id'],
force=force)
def promote_share_replica(self, context, share_replica_id, host,
share_id=None):
new_host = utils.extract_host(host)
call_context = self.client.prepare(server=new_host, version='1.8')
def promote_share_replica(self, context, share_replica):
host = utils.extract_host(share_replica['host'])
call_context = self.client.prepare(server=host, version='1.8')
call_context.cast(context,
'promote_share_replica',
share_replica_id=share_replica_id,
share_id=share_id)
share_replica_id=share_replica['id'],
share_id=share_replica['share_id'])
def update_share_replica(self, context, share_replica):
host = utils.extract_host(share_replica['host'])
call_context = self.client.prepare(server=host, version='1.8')
call_context.cast(context,
'update_share_replica',
share_replica_id=share_replica['id'],
share_id=share_replica['share_id'])

View File

@ -208,6 +208,49 @@ fixture_reset_status_with_different_roles = (
)
fixture_reset_replica_status_with_different_roles = (
{
'role': 'admin',
'valid_code': 202,
'valid_status': constants.STATUS_ERROR,
},
{
'role': 'member',
'valid_code': 403,
'valid_status': constants.STATUS_AVAILABLE,
},
)
fixture_reset_replica_state_with_different_roles = (
{
'role': 'admin',
'valid_code': 202,
'valid_status': constants.REPLICA_STATE_ACTIVE,
},
{
'role': 'admin',
'valid_code': 202,
'valid_status': constants.REPLICA_STATE_OUT_OF_SYNC,
},
{
'role': 'admin',
'valid_code': 202,
'valid_status': constants.REPLICA_STATE_IN_SYNC,
},
{
'role': 'admin',
'valid_code': 202,
'valid_status': constants.STATUS_ERROR,
},
{
'role': 'member',
'valid_code': 403,
'valid_status': constants.REPLICA_STATE_IN_SYNC,
},
)
fixture_force_delete_with_different_roles = (
{'role': 'admin', 'resp_code': 202, 'version': '2.6'},
{'role': 'admin', 'resp_code': 202, 'version': '2.7'},

View File

@ -16,16 +16,19 @@
import ddt
import mock
from oslo_config import cfg
from oslo_serialization import jsonutils
import six
from webob import exc
from manila.api.v2 import share_replicas
from manila.common import constants
from manila import context
from manila import exception
from manila import policy
from manila import share
from manila import test
from manila.tests.api import fakes
from manila.tests import db_utils
from manila.tests import fake_share
CONF = cfg.CONF
@ -42,13 +45,33 @@ class ShareReplicasApiTest(test.TestCase):
self.replicas_req = fakes.HTTPRequest.blank(
'/share-replicas', version=self.api_version,
experimental=True)
self.context = self.replicas_req.environ['manila.context']
self.member_context = context.RequestContext('fake', 'fake')
self.replicas_req.environ['manila.context'] = self.member_context
self.replicas_req_admin = fakes.HTTPRequest.blank(
'/share-replicas', version=self.api_version,
experimental=True, use_admin_context=True)
self.admin_context = self.replicas_req_admin.environ['manila.context']
self.mock_policy_check = self.mock_object(policy, 'check_policy')
def _get_context(self, role):
return getattr(self, '%s_context' % role)
def _create_replica_get_req(self, **kwargs):
if 'status' not in kwargs:
kwargs['status'] = constants.STATUS_AVAILABLE
if 'replica_state' not in kwargs:
kwargs['replica_state'] = constants.REPLICA_STATE_IN_SYNC
replica = db_utils.create_share_replica(**kwargs)
req = fakes.HTTPRequest.blank(
'/v2/fake/share-replicas/%s/action' % replica['id'],
version=self.api_version)
req.method = 'POST'
req.headers['content-type'] = 'application/json'
req.headers['X-Openstack-Manila-Api-Version'] = self.api_version
req.headers['X-Openstack-Manila-Api-Experimental'] = True
return replica, req
def _get_fake_replica(self, summary=False, admin=False, **values):
replica = fake_share.fake_replica(**values)
replica['updated_at'] = '2016-02-11T19:57:56.506805'
@ -79,7 +102,7 @@ class ShareReplicasApiTest(test.TestCase):
self.assertEqual([expected_replica], res_dict['share_replicas'])
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'get_all')
self.member_context, self.resource_name, 'get_all')
def test_list_share_replicas_summary(self):
fake_replica, expected_replica = self._get_fake_replica(summary=True)
@ -162,7 +185,7 @@ class ShareReplicasApiTest(test.TestCase):
self.controller.detail, req)
self.assertFalse(mock__view_builder_call.called)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'get_all')
self.member_context, self.resource_name, 'get_all')
@ddt.data(True, False)
def test_list_share_replicas_detail(self, is_admin):
@ -173,7 +196,7 @@ class ShareReplicasApiTest(test.TestCase):
'/share-replicas?share_id=FAKE_SHARE_ID',
version=self.api_version, experimental=True)
req.environ['manila.context'] = (
self.context if not is_admin else self.admin_context)
self.member_context if not is_admin else self.admin_context)
req_context = req.environ['manila.context']
res_dict = self.controller.detail(req)
@ -250,7 +273,7 @@ class ShareReplicasApiTest(test.TestCase):
'FAKE_REPLICA_ID')
self.assertFalse(mock__view_builder_call.called)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'show')
self.member_context, self.resource_name, 'show')
def test_create_invalid_body(self):
body = {}
@ -263,7 +286,7 @@ class ShareReplicasApiTest(test.TestCase):
self.replicas_req, body)
self.assertEqual(0, mock__view_builder_call.call_count)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'create')
self.member_context, self.resource_name, 'create')
def test_create_no_share_id(self):
body = {
@ -281,7 +304,7 @@ class ShareReplicasApiTest(test.TestCase):
self.replicas_req, body)
self.assertFalse(mock__view_builder_call.called)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'create')
self.member_context, self.resource_name, 'create')
def test_create_invalid_share_id(self):
body = {
@ -301,7 +324,7 @@ class ShareReplicasApiTest(test.TestCase):
self.replicas_req, body)
self.assertFalse(mock__view_builder_call.called)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'create')
self.member_context, self.resource_name, 'create')
@ddt.data(exception.AvailabilityZoneNotFound,
exception.ReplicationException, exception.ShareBusyException)
@ -328,7 +351,7 @@ class ShareReplicasApiTest(test.TestCase):
self.replicas_req, body)
self.assertFalse(mock__view_builder_call.called)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'create')
self.member_context, self.resource_name, 'create')
@ddt.data(True, False)
def test_create(self, is_admin):
@ -370,7 +393,7 @@ class ShareReplicasApiTest(test.TestCase):
self.replicas_req, 'FAKE_REPLICA_ID')
self.assertFalse(mock_delete_replica_call.called)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'delete')
self.member_context, self.resource_name, 'delete')
def test_delete_exception(self):
fake_replica_1 = self._get_fake_replica(
@ -391,7 +414,7 @@ class ShareReplicasApiTest(test.TestCase):
self.assertRaises(exc.HTTPBadRequest, self.controller.delete,
self.replicas_req, 'FAKE_REPLICA_ID')
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'delete')
self.member_context, self.resource_name, 'delete')
def test_delete(self):
fake_replica = self._get_fake_replica(
@ -406,7 +429,7 @@ class ShareReplicasApiTest(test.TestCase):
self.assertEqual(202, resp.status_code)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'delete')
self.member_context, self.resource_name, 'delete')
def test_promote_invalid_replica_id(self):
body = {'promote': None}
@ -420,7 +443,7 @@ class ShareReplicasApiTest(test.TestCase):
self.replicas_req,
'FAKE_REPLICA_ID', body)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'promote')
self.member_context, self.resource_name, 'promote')
def test_promote_already_active(self):
body = {'promote': None}
@ -436,7 +459,7 @@ class ShareReplicasApiTest(test.TestCase):
self.assertEqual(200, resp.status_code)
self.assertFalse(mock_api_promote_replica_call.called)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'promote')
self.member_context, self.resource_name, 'promote')
def test_promote_replication_exception(self):
body = {'promote': None}
@ -456,7 +479,7 @@ class ShareReplicasApiTest(test.TestCase):
body)
self.assertTrue(mock_api_promote_replica_call.called)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'promote')
self.member_context, self.resource_name, 'promote')
def test_promote_admin_required_exception(self):
body = {'promote': None}
@ -475,7 +498,7 @@ class ShareReplicasApiTest(test.TestCase):
body)
self.assertTrue(mock_api_promote_replica_call.called)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'promote')
self.member_context, self.resource_name, 'promote')
def test_promote(self):
body = {'promote': None}
@ -492,9 +515,10 @@ class ShareReplicasApiTest(test.TestCase):
self.assertEqual(expected_replica, resp['share_replica'])
self.assertTrue(mock_api_promote_replica_call.called)
self.mock_policy_check.assert_called_once_with(
self.context, self.resource_name, 'promote')
self.member_context, self.resource_name, 'promote')
@ddt.data('index', 'detail', 'show', 'create', 'delete', 'promote')
@ddt.data('index', 'detail', 'show', 'create', 'delete', 'promote',
'reset_replica_state', 'reset_status', 'resync')
def test_policy_not_authorized(self, method_name):
method = getattr(self.controller, method_name)
@ -513,10 +537,11 @@ class ShareReplicasApiTest(test.TestCase):
self.assertRaises(
exc.HTTPForbidden, method, self.replicas_req, **arguments)
@ddt.data('index', 'detail', 'show', 'create', 'delete', 'promote')
@ddt.data('index', 'detail', 'show', 'create', 'delete', 'promote',
'reset_replica_state', 'reset_status', 'resync')
def test_upsupported_microversion(self, method_name):
unsupported_microversions = ('1.0', '2.2', '2.8')
unsupported_microversions = ('1.0', '2.2', '2.10')
method = getattr(self.controller, method_name)
arguments = {
'id': 'FAKE_REPLICA_ID',
@ -531,3 +556,178 @@ class ShareReplicasApiTest(test.TestCase):
experimental=True)
self.assertRaises(exception.VersionNotFoundForAPIMethod,
method, req, **arguments)
def _reset_status(self, context, replica, req,
valid_code=202, status_attr='status',
valid_status=None, body=None):
if status_attr == 'status':
action_name = 'reset_status'
body = body or {action_name: {'status': constants.STATUS_ERROR}}
else:
action_name = 'reset_replica_state'
body = body or {
action_name: {'replica_state': constants.STATUS_ERROR},
}
req.body = six.b(jsonutils.dumps(body))
req.environ['manila.context'] = context
with mock.patch.object(
policy, 'check_policy', fakes.mock_fake_admin_check):
resp = req.get_response(fakes.app())
# validate response code and model status
self.assertEqual(valid_code, resp.status_int)
if valid_code == 404:
self.assertRaises(exception.ShareReplicaNotFound,
share_replicas.db.share_replica_get,
context,
replica['id'])
else:
actual_replica = share_replicas.db.share_replica_get(
context, replica['id'])
self.assertEqual(valid_status, actual_replica[status_attr])
@ddt.data(*fakes.fixture_reset_replica_status_with_different_roles)
@ddt.unpack
def test_reset_status_with_different_roles(self, role, valid_code,
valid_status):
context = self._get_context(role)
replica, action_req = self._create_replica_get_req()
self._reset_status(context, replica, action_req,
valid_code=valid_code, status_attr='status',
valid_status=valid_status)
@ddt.data(
{'os-reset_status': {'x-status': 'bad'}},
{'os-reset_status': {'status': constants.STATUS_AVAILABLE}},
{'reset_status': {'x-status': 'bad'}},
{'reset_status': {'status': 'invalid'}},
)
def test_reset_status_invalid_body(self, body):
replica, action_req = self._create_replica_get_req()
self._reset_status(self.admin_context, replica, action_req,
valid_code=400, status_attr='status',
valid_status=constants.STATUS_AVAILABLE, body=body)
@ddt.data(*fakes.fixture_reset_replica_state_with_different_roles)
@ddt.unpack
def test_reset_replica_state_with_different_roles(self, role, valid_code,
valid_status):
context = self._get_context(role)
replica, action_req = self._create_replica_get_req()
body = {'reset_replica_state': {'replica_state': valid_status}}
self._reset_status(context, replica, action_req,
valid_code=valid_code, status_attr='replica_state',
valid_status=valid_status, body=body)
@ddt.data(
{'os-reset_replica_state': {'x-replica_state': 'bad'}},
{'os-reset_replica_state': {'replica_state': constants.STATUS_ERROR}},
{'reset_replica_state': {'x-replica_state': 'bad'}},
{'reset_replica_state': {'replica_state': constants.STATUS_AVAILABLE}},
)
def test_reset_replica_state_invalid_body(self, body):
replica, action_req = self._create_replica_get_req()
self._reset_status(self.admin_context, replica, action_req,
valid_code=400, status_attr='status',
valid_status=constants.STATUS_AVAILABLE, body=body)
def _force_delete(self, context, req, valid_code=202):
body = {'force_delete': {}}
req.environ['manila.context'] = context
req.body = six.b(jsonutils.dumps(body))
with mock.patch.object(
policy, 'check_policy', fakes.mock_fake_admin_check):
resp = req.get_response(fakes.app())
# validate response
self.assertEqual(valid_code, resp.status_int)
@ddt.data(*fakes.fixture_force_delete_with_different_roles)
@ddt.unpack
def test_force_delete_replica_with_different_roles(self, role, resp_code,
version):
replica, req = self._create_replica_get_req()
context = self._get_context(role)
self._force_delete(context, req, valid_code=resp_code)
def test_force_delete_missing_replica(self):
replica, req = self._create_replica_get_req()
share_replicas.db.share_replica_delete(
self.admin_context, replica['id'])
self._force_delete(self.admin_context, req, valid_code=404)
def test_resync_replica_not_found(self):
replica, req = self._create_replica_get_req()
share_replicas.db.share_replica_delete(
self.admin_context, replica['id'])
share_api_call = self.mock_object(self.controller.share_api,
'update_share_replica')
body = {'resync': {}}
req.body = six.b(jsonutils.dumps(body))
req.environ['manila.context'] = self.admin_context
with mock.patch.object(
policy, 'check_policy', fakes.mock_fake_admin_check):
resp = req.get_response(fakes.app())
self.assertEqual(404, resp.status_int)
self.assertFalse(share_api_call.called)
def test_resync_API_exception(self):
replica, req = self._create_replica_get_req(
replica_state=constants.REPLICA_STATE_OUT_OF_SYNC)
self.mock_object(share_replicas.db, 'share_replica_get',
mock.Mock(return_value=replica))
share_api_call = self.mock_object(
share.API, 'update_share_replica', mock.Mock(
side_effect=exception.InvalidHost(reason='')))
body = {'resync': None}
req.body = six.b(jsonutils.dumps(body))
req.environ['manila.context'] = self.admin_context
with mock.patch.object(
policy, 'check_policy', fakes.mock_fake_admin_check):
resp = req.get_response(fakes.app())
self.assertEqual(400, resp.status_int)
share_api_call.assert_called_once_with(self.admin_context, replica)
@ddt.data(constants.REPLICA_STATE_ACTIVE,
constants.REPLICA_STATE_IN_SYNC,
constants.REPLICA_STATE_OUT_OF_SYNC,
constants.STATUS_ERROR)
def test_resync(self, replica_state):
replica, req = self._create_replica_get_req(
replica_state=replica_state, host='skywalker@jedi#temple')
share_api_call = self.mock_object(
share.API, 'update_share_replica', mock.Mock(return_value=None))
body = {'resync': {}}
req.body = six.b(jsonutils.dumps(body))
req.environ['manila.context'] = self.admin_context
with mock.patch.object(
policy, 'check_policy', fakes.mock_fake_admin_check):
resp = req.get_response(fakes.app())
if replica_state == constants.REPLICA_STATE_ACTIVE:
self.assertEqual(200, resp.status_int)
self.assertFalse(share_api_call.called)
else:
self.assertEqual(202, resp.status_int)
self.assertTrue(share_api_call.called)

View File

@ -106,6 +106,10 @@ def create_share_replica(**kwargs):
}
replica.update(kwargs)
if 'share_id' not in kwargs:
share = create_share()
kwargs['share_id'] = share['id']
return db.share_instance_create(context.get_admin_context(),
kwargs.pop('share_id'), kwargs)

View File

@ -98,5 +98,15 @@
"consistency_group:get_all_cgsnapshots": "rule:default",
"cgsnapshot:force_delete": "rule:admin_api",
"cgsnapshot:reset_status": "rule:admin_api"
"cgsnapshot:reset_status": "rule:admin_api",
"share_replica:get_all": "rule:default",
"share_replica:show": "rule:default",
"share_replica:create" : "rule:default",
"share_replica:delete": "rule:default",
"share_replica:promote": "rule:default",
"share_replica:resync": "rule:admin_api",
"share_replica:reset_status": "rule:admin_api",
"share_replica:force_delete": "rule:admin_api",
"share_replica:reset_replica_state": "rule:admin_api"
}

View File

@ -1889,8 +1889,7 @@ class ShareAPITestCase(test.TestCase):
self.api.delete_share_replica(self.context, replica, force=force)
mock_sched_rpcapi_call.assert_called_once_with(
self.context, replica['id'],
'HOSTA@BackendB', share_id=replica['share_id'], force=force)
self.context, replica, force=force)
mock_db_update_call.assert_called_once_with(
self.context, replica['id'],
{'status': constants.STATUS_DELETING,
@ -1903,8 +1902,6 @@ class ShareAPITestCase(test.TestCase):
def test_promote_share_replica_non_available_status(self, status):
replica = fakes.fake_replica(
status=status, replica_state=constants.REPLICA_STATE_IN_SYNC)
mock_extract_host_call = self.mock_object(
share_api.share_utils, 'extract_host')
mock_rpcapi_promote_share_replica_call = self.mock_object(
self.share_rpcapi, 'promote_share_replica')
@ -1912,7 +1909,6 @@ class ShareAPITestCase(test.TestCase):
self.api.promote_share_replica,
self.context,
replica)
self.assertFalse(mock_extract_host_call.called)
self.assertFalse(mock_rpcapi_promote_share_replica_call.called)
@ddt.data(constants.REPLICA_STATE_OUT_OF_SYNC, constants.STATUS_ERROR)
@ -1923,8 +1919,6 @@ class ShareAPITestCase(test.TestCase):
replica = fakes.fake_replica(
status=constants.STATUS_AVAILABLE,
replica_state=replica_state)
mock_extract_host_call = self.mock_object(
share_api.share_utils, 'extract_host')
mock_rpcapi_promote_share_replica_call = self.mock_object(
self.share_rpcapi, 'promote_share_replica')
@ -1932,7 +1926,6 @@ class ShareAPITestCase(test.TestCase):
self.api.promote_share_replica,
fake_user_context,
replica)
self.assertFalse(mock_extract_host_call.called)
self.assertFalse(mock_rpcapi_promote_share_replica_call.called)
@ddt.data(constants.REPLICA_STATE_OUT_OF_SYNC, constants.STATUS_ERROR)
@ -1942,9 +1935,6 @@ class ShareAPITestCase(test.TestCase):
replica_state=replica_state, host='HOSTA@BackendB#PoolC')
self.mock_object(db_api, 'share_replica_get',
mock.Mock(return_value=replica))
mock_extract_host_call = self.mock_object(
share_api.share_utils, 'extract_host',
mock.Mock(return_value='HOSTA'))
mock_rpcapi_promote_share_replica_call = self.mock_object(
self.share_rpcapi, 'promote_share_replica')
mock_db_update_call = self.mock_object(db_api, 'share_replica_update')
@ -1953,31 +1943,48 @@ class ShareAPITestCase(test.TestCase):
self.context, replica)
self.assertEqual(replica, retval)
self.assertTrue(mock_extract_host_call.called)
mock_db_update_call.assert_called_once_with(
self.context, replica['id'],
{'status': constants.STATUS_REPLICATION_CHANGE})
mock_rpcapi_promote_share_replica_call.assert_called_once_with(
self.context, replica['id'], 'HOSTA', share_id=replica['share_id'])
self.context, replica)
def test_promote_share_replica(self):
replica = fakes.fake_replica('FAKE_ID', host='HOSTA@BackendB#PoolC')
self.mock_object(db_api, 'share_replica_get',
mock.Mock(return_value=replica))
self.mock_object(db_api, 'share_replica_update')
mock_extract_host_call = self.mock_object(
share_api.share_utils, 'extract_host',
mock.Mock(return_value='HOSTA'))
mock_sched_rpcapi_call = self.mock_object(
self.share_rpcapi, 'promote_share_replica')
result = self.api.promote_share_replica(self.context, replica)
mock_sched_rpcapi_call.assert_called_once_with(
self.context, replica['id'], 'HOSTA', share_id=replica['share_id'])
mock_extract_host_call.assert_called_once_with('HOSTA@BackendB#PoolC')
self.context, replica)
self.assertEqual(replica, result)
def test_update_share_replica_no_host(self):
replica = fakes.fake_replica('FAKE_ID')
replica['host'] = None
mock_rpcapi_update_share_replica_call = self.mock_object(
self.share_rpcapi, 'update_share_replica')
self.assertRaises(exception.InvalidHost,
self.api.update_share_replica,
self.context,
replica)
self.assertFalse(mock_rpcapi_update_share_replica_call.called)
def test_update_share_replica(self):
replica = fakes.fake_replica('FAKE_ID', host='HOSTA@BackendB#PoolC')
mock_rpcapi_update_share_replica_call = self.mock_object(
self.share_rpcapi, 'update_share_replica')
retval = self.api.update_share_replica(self.context, replica)
self.assertTrue(mock_rpcapi_update_share_replica_call.called)
self.assertIsNone(retval)
class OtherTenantsShareActionsTestCase(test.TestCase):
def setUp(self):

View File

@ -805,14 +805,14 @@ class ShareDriverTestCase(test.TestCase):
share_driver = self._instantiate_share_driver(None, True)
self.assertRaises(NotImplementedError,
share_driver.create_replica,
'fake_context', 'fake_active_replica',
'fake_context', ['r1', 'r2'],
'fake_new_replica', [])
def test_delete_replica(self):
share_driver = self._instantiate_share_driver(None, True)
self.assertRaises(NotImplementedError,
share_driver.delete_replica,
'fake_context', 'fake_active_replica',
'fake_context', ['r1', 'r2'],
'fake_replica')
def test_promote_replica(self):
@ -825,4 +825,4 @@ class ShareDriverTestCase(test.TestCase):
share_driver = self._instantiate_share_driver(None, True)
self.assertRaises(NotImplementedError,
share_driver.update_replica_state,
'fake_context', [], 'fake_replica')
'fake_context', ['r1', 'r2'], 'fake_replica', [])

View File

@ -203,6 +203,7 @@ class ShareManagerTestCase(test.TestCase):
"delete_share_replica",
"promote_share_replica",
"periodic_share_replica_update",
"update_share_replica",
)
def test_call_driver_when_its_init_failed(self, method_name):
self.mock_object(self.share_manager.driver, 'do_setup',
@ -602,12 +603,15 @@ class ShareManagerTestCase(test.TestCase):
def test_create_share_replica_driver_error_on_creation(self):
fake_access_rules = [{'id': '1'}, {'id': '2'}, {'id': '3'}]
replica = fake_replica(share_network_id='')
replica_2 = fake_replica(id='fake2')
self.mock_object(db, 'share_replica_get',
mock.Mock(return_value=replica))
self.mock_object(db, 'share_instance_access_copy',
mock.Mock(return_value=fake_access_rules))
self.mock_object(db, 'share_replicas_get_available_active_replica',
mock.Mock(return_value=fake_replica(id='fake2')))
mock.Mock(return_value=replica_2))
self.mock_object(db, 'share_replicas_get_all_by_share',
mock.Mock(return_value=[replica, replica_2]))
self.mock_object(self.share_manager,
'_provide_share_server_for_share',
mock.Mock(return_value=('FAKE_SERVER', replica)))
@ -622,8 +626,9 @@ class ShareManagerTestCase(test.TestCase):
db, 'share_instance_update_access_status')
self.mock_object(self.share_manager, '_get_share_server')
self.mock_object(self.share_manager.driver, 'create_replica',
mock.Mock(side_effect=exception.ManilaException))
driver_call = self.mock_object(
self.share_manager.driver, 'create_replica',
mock.Mock(side_effect=exception.ManilaException))
self.assertRaises(exception.ManilaException,
self.share_manager.create_share_replica,
@ -635,15 +640,19 @@ class ShareManagerTestCase(test.TestCase):
self.assertFalse(mock_export_locs_update_call.called)
self.assertTrue(mock_log_error.called)
self.assertFalse(mock_log_info.called)
self.assertTrue(driver_call.called)
def test_create_share_replica_invalid_locations_state(self):
driver_retval = {
'export_locations': 'FAKE_EXPORT_LOC',
}
replica = fake_replica(share_network='')
replica_2 = fake_replica(id='fake2')
fake_access_rules = [{'id': '1'}, {'id': '2'}]
self.mock_object(db, 'share_replicas_get_available_active_replica',
mock.Mock(return_value=fake_replica(id='fake2')))
mock.Mock(return_value=replica_2))
self.mock_object(db, 'share_replicas_get_all_by_share',
mock.Mock(return_value=[replica, replica_2]))
self.mock_object(db, 'share_replica_get',
mock.Mock(return_value=replica))
self.mock_object(db, 'share_instance_access_copy',
@ -651,15 +660,17 @@ class ShareManagerTestCase(test.TestCase):
self.mock_object(self.share_manager,
'_provide_share_server_for_share',
mock.Mock(return_value=('FAKE_SERVER', replica)))
self.mock_object(self.share_manager, '_get_share_server')
self.mock_object(self.share_manager, '_get_share_server',
mock.Mock(return_value=None))
mock_replica_update_call = self.mock_object(db, 'share_replica_update')
mock_export_locs_update_call = self.mock_object(
db, 'share_export_locations_update')
mock_log_info = self.mock_object(manager.LOG, 'info')
mock_log_warning = self.mock_object(manager.LOG, 'warning')
mock_log_error = self.mock_object(manager.LOG, 'error')
self.mock_object(self.share_manager.driver, 'create_replica',
mock.Mock(return_value=driver_retval))
driver_call = self.mock_object(
self.share_manager.driver, 'create_replica',
mock.Mock(return_value=driver_retval))
self.mock_object(db, 'share_instance_access_get',
mock.Mock(return_value=fake_access_rules[0]))
mock_share_replica_access_update = self.mock_object(
@ -673,11 +684,21 @@ class ShareManagerTestCase(test.TestCase):
self.assertTrue(mock_log_info.called)
self.assertTrue(mock_log_warning.called)
self.assertFalse(mock_log_error.called)
self.assertTrue(driver_call.called)
call_args = driver_call.call_args_list[0][0]
replica_list_arg = call_args[1]
r_ids = [r['id'] for r in replica_list_arg]
for r in (replica, replica_2):
self.assertIn(r['id'], r_ids)
self.assertEqual(2, len(r_ids))
def test_create_share_replica_no_availability_zone(self):
replica = fake_replica(
availability_zone=None, share_network='',
replica_state=constants.REPLICA_STATE_OUT_OF_SYNC)
replica_2 = fake_replica(id='fake2')
self.mock_object(db, 'share_replicas_get_all_by_share',
mock.Mock(return_value=[replica, replica_2]))
manager.CONF.set_default('storage_availability_zone', 'fake_az')
fake_access_rules = [{'id': '1'}, {'id': '2'}, {'id': '3'}]
self.mock_object(db, 'share_replica_get',
@ -685,7 +706,7 @@ class ShareManagerTestCase(test.TestCase):
self.mock_object(db, 'share_instance_access_copy',
mock.Mock(return_value=fake_access_rules))
self.mock_object(db, 'share_replicas_get_available_active_replica',
mock.Mock(return_value=fake_replica(id='fake2')))
mock.Mock(return_value=replica_2))
self.mock_object(self.share_manager,
'_provide_share_server_for_share',
mock.Mock(return_value=('FAKE_SERVER', replica)))
@ -707,10 +728,10 @@ class ShareManagerTestCase(test.TestCase):
mock.Mock(return_value=fake_access_rules[0]))
mock_share_replica_access_update = self.mock_object(
self.share_manager, '_update_share_replica_access_rules_state')
self.mock_object(
driver_call = self.mock_object(
self.share_manager.driver, 'create_replica',
mock.Mock(return_value=replica))
self.mock_object(self.share_manager, '_get_share_server')
self.mock_object(self.share_manager, '_get_share_server', mock.Mock())
self.share_manager.create_share_replica(self.context, replica)
@ -721,10 +742,12 @@ class ShareManagerTestCase(test.TestCase):
self.assertTrue(mock_log_info.called)
self.assertFalse(mock_log_warning.called)
self.assertFalse(mock_log_error.called)
self.assertTrue(driver_call.called)
def test_create_share_replica(self):
replica = fake_replica(
share_network='', replica_state=constants.REPLICA_STATE_IN_SYNC)
replica_2 = fake_replica(id='fake2')
fake_access_rules = [{'id': '1'}, {'id': '2'}, {'id': '3'}]
self.mock_object(db, 'share_replica_get',
mock.Mock(return_value=replica))
@ -735,6 +758,8 @@ class ShareManagerTestCase(test.TestCase):
self.mock_object(self.share_manager,
'_provide_share_server_for_share',
mock.Mock(return_value=('FAKE_SERVER', replica)))
self.mock_object(db, 'share_replicas_get_all_by_share',
mock.Mock(return_value=[replica, replica_2]))
mock_replica_update_call = self.mock_object(db, 'share_replica_update')
mock_export_locs_update_call = self.mock_object(
db, 'share_export_locations_update')
@ -745,7 +770,7 @@ class ShareManagerTestCase(test.TestCase):
mock.Mock(return_value=fake_access_rules[0]))
mock_share_replica_access_update = self.mock_object(
db, 'share_instance_update_access_status')
self.mock_object(
driver_call = self.mock_object(
self.share_manager.driver, 'create_replica',
mock.Mock(return_value=replica))
self.mock_object(self.share_manager, '_get_share_server')
@ -761,9 +786,19 @@ class ShareManagerTestCase(test.TestCase):
self.assertTrue(mock_log_info.called)
self.assertFalse(mock_log_warning.called)
self.assertFalse(mock_log_error.called)
self.assertTrue(driver_call.called)
call_args = driver_call.call_args_list[0][0]
replica_list_arg = call_args[1]
r_ids = [r['id'] for r in replica_list_arg]
for r in (replica, replica_2):
self.assertIn(r['id'], r_ids)
self.assertEqual(2, len(r_ids))
def test_delete_share_replica_access_rules_exception(self):
replica = fake_replica()
replica_2 = fake_replica(id='fake_2')
self.mock_object(db, 'share_replicas_get_all_by_share',
mock.Mock(return_value=[replica, replica_2]))
active_replica = fake_replica(id='Current_active_replica')
mock_error_log = self.mock_object(manager.LOG, 'error')
self.mock_object(db, 'share_replica_get',
@ -794,11 +829,14 @@ class ShareManagerTestCase(test.TestCase):
replica = fake_replica()
active_replica = fake_replica(id='Current_active_replica')
mock_error_log = self.mock_object(manager.LOG, 'error')
self.mock_object(db, 'share_replicas_get_all_by_share',
mock.Mock(return_value=[replica, active_replica]))
self.mock_object(db, 'share_replica_get',
mock.Mock(return_value=replica))
self.mock_object(db, 'share_replicas_get_available_active_replica',
mock.Mock(return_value=active_replica))
self.mock_object(self.share_manager, '_get_share_server')
self.mock_object(self.share_manager, '_get_share_server',
mock.Mock(return_value=None))
self.mock_object(self.share_manager.access_helper,
'update_access_rules')
mock_replica_update_call = self.mock_object(db, 'share_replica_update')
@ -813,70 +851,56 @@ class ShareManagerTestCase(test.TestCase):
self.context, replica, share_id=replica['share_id'], force=True)
self.assertFalse(mock_replica_update_call.called)
self.assertTrue(mock_drv_delete_replica_call.called)
self.assertTrue(mock_replica_delete_call.called)
self.assertEqual(1, mock_error_log.call_count)
self.assertTrue(mock_drv_delete_replica_call.called)
def test_delete_share_replica_driver_exception(self):
replica = fake_replica()
active_replica = fake_replica(id='Current_active_replica')
self.mock_object(db, 'share_replicas_get_all_by_share',
mock.Mock(return_value=[replica, active_replica]))
self.mock_object(db, 'share_replica_get',
mock.Mock(return_value=replica))
self.mock_object(db, 'share_replicas_get_available_active_replica',
mock.Mock(return_value=active_replica))
self.mock_object(self.share_manager, '_get_share_server')
self.mock_object(self.share_manager, '_get_share_server',
mock.Mock(return_value=None))
mock_replica_update_call = self.mock_object(db, 'share_replica_update')
mock_replica_delete_call = self.mock_object(db, 'share_replica_delete')
self.mock_object(
self.share_manager.access_helper, 'update_access_rules')
self.mock_object(self.share_manager.driver, 'delete_replica',
mock.Mock(side_effect=exception.ManilaException))
mock_drv_delete_replica_call = self.mock_object(
self.share_manager.driver, 'delete_replica',
mock.Mock(side_effect=exception.ManilaException))
self.assertRaises(exception.ManilaException,
self.share_manager.delete_share_replica,
self.context, replica)
self.assertTrue(mock_replica_update_call.called)
self.assertFalse(mock_replica_delete_call.called)
def test_delete_share_replica_drv_exception_ignored_with_the_force(self):
replica = fake_replica()
active_replica = fake_replica(id='Current_active_replica')
mock_error_log = self.mock_object(manager.LOG, 'error')
self.mock_object(db, 'share_replica_get',
mock.Mock(return_value=replica))
self.mock_object(db, 'share_replicas_get_available_active_replica',
mock.Mock(return_value=active_replica))
self.mock_object(self.share_manager, '_get_share_server')
mock_replica_update_call = self.mock_object(db, 'share_replica_update')
mock_replica_delete_call = self.mock_object(db, 'share_replica_delete')
self.mock_object(
self.share_manager.access_helper, 'update_access_rules')
self.mock_object(self.share_manager.driver, 'delete_replica',
mock.Mock(side_effect=exception.ManilaException))
self.share_manager.delete_share_replica(
self.context, replica, share_id=replica['share_id'], force=True)
self.assertFalse(mock_replica_update_call.called)
self.assertTrue(mock_replica_delete_call.called)
self.assertEqual(1, mock_error_log.call_count)
self.assertTrue(mock_drv_delete_replica_call.called)
def test_delete_share_replica_both_exceptions_ignored_with_the_force(self):
replica = fake_replica()
active_replica = fake_replica(id='Current_active_replica')
self.mock_object(db, 'share_replicas_get_all_by_share',
mock.Mock(return_value=[replica, active_replica]))
mock_error_log = self.mock_object(manager.LOG, 'error')
self.mock_object(db, 'share_replica_get',
mock.Mock(return_value=replica))
self.mock_object(db, 'share_replicas_get_available_active_replica',
mock.Mock(return_value=active_replica))
self.mock_object(self.share_manager, '_get_share_server')
self.mock_object(self.share_manager, '_get_share_server',
mock.Mock(return_value=None))
mock_replica_update_call = self.mock_object(db, 'share_replica_update')
mock_replica_delete_call = self.mock_object(db, 'share_replica_delete')
self.mock_object(
self.share_manager.access_helper, 'update_access_rules',
mock.Mock(side_effect=exception.ManilaException))
self.mock_object(self.share_manager.driver, 'delete_replica',
mock.Mock(side_effect=exception.ManilaException))
mock_drv_delete_replica_call = self.mock_object(
self.share_manager.driver, 'delete_replica',
mock.Mock(side_effect=exception.ManilaException))
self.share_manager.delete_share_replica(
self.context, replica, share_id=replica['share_id'], force=True)
@ -885,27 +909,33 @@ class ShareManagerTestCase(test.TestCase):
mock.ANY, replica['id'], {'status': constants.STATUS_ERROR})
self.assertTrue(mock_replica_delete_call.called)
self.assertEqual(2, mock_error_log.call_count)
self.assertTrue(mock_drv_delete_replica_call.called)
def test_delete_share_replica(self):
replica = fake_replica()
active_replica = fake_replica(id='current_active_replica')
self.mock_object(db, 'share_replicas_get_all_by_share',
mock.Mock(return_value=[replica, active_replica]))
self.mock_object(db, 'share_replica_get',
mock.Mock(return_value=replica))
self.mock_object(db, 'share_replicas_get_available_active_replica',
mock.Mock(return_value=active_replica))
self.mock_object(self.share_manager, '_get_share_server')
self.mock_object(self.share_manager, '_get_share_server',
mock.Mock(return_value=None))
mock_info_log = self.mock_object(manager.LOG, 'info')
mock_replica_update_call = self.mock_object(db, 'share_replica_update')
mock_replica_delete_call = self.mock_object(db, 'share_replica_delete')
self.mock_object(
self.share_manager.access_helper, 'update_access_rules')
self.mock_object(self.share_manager.driver, 'delete_replica')
mock_drv_delete_replica_call = self.mock_object(
self.share_manager.driver, 'delete_replica')
self.share_manager.delete_share_replica(self.context, replica)
self.assertFalse(mock_replica_update_call.called)
self.assertTrue(mock_replica_delete_call.called)
self.assertTrue(mock_info_log.called)
self.assertTrue(mock_drv_delete_replica_call.called)
def test_promote_share_replica_no_active_replica(self):
replica = fake_replica()
@ -1010,9 +1040,8 @@ class ShareManagerTestCase(test.TestCase):
'replica_state': constants.REPLICA_STATE_IN_SYNC,
},
{
'id': 'current_active_replica',
'id': 'other_replica',
'export_locations': ['TEST1', 'TEST2'],
'replica_state': constants.STATUS_ERROR,
},
]
self.mock_object(db, 'share_replica_get',
@ -1036,32 +1065,11 @@ class ShareManagerTestCase(test.TestCase):
self.share_manager.promote_share_replica(self.context, replica)
self.assertEqual(2, mock_export_locs_update.call_count)
self.assertEqual(3, mock_replica_update.call_count)
self.assertEqual(2, mock_replica_update.call_count)
self.assertTrue(
reset_replication_change_call in mock_replica_update.mock_calls)
self.assertTrue(mock_info_log.called)
@ddt.data(constants.REPLICA_STATE_IN_SYNC,
constants.REPLICA_STATE_OUT_OF_SYNC)
def test_update_share_replica_state_driver_exception(self, replica_state):
mock_debug_log = self.mock_object(manager.LOG, 'debug')
replica = fake_replica(replica_state=replica_state)
self.mock_object(self.share_manager.db, 'share_replicas_get_all',
mock.Mock(return_value=[replica]))
self.mock_object(db, 'share_server_get',
mock.Mock(return_value='fake_share_server'))
self.share_manager.host = replica['host']
self.mock_object(self.share_manager.driver, 'update_replica_state',
mock.Mock(side_effect=exception.ManilaException))
mock_db_update_call = self.mock_object(
self.share_manager.db, 'share_replica_update')
self.assertRaises(exception.ManilaException,
self.share_manager.periodic_share_replica_update,
self.context)
self.assertFalse(mock_db_update_call.called)
self.assertEqual(1, mock_debug_log.call_count)
@ddt.data('openstack1@watson#_pool0', 'openstack1@newton#_pool0')
def test_periodic_share_replica_update(self, host):
mock_debug_log = self.mock_object(manager.LOG, 'debug')
@ -1084,9 +1092,33 @@ class ShareManagerTestCase(test.TestCase):
self.assertEqual(2, mock_update_method.call_count)
self.assertEqual(1, mock_debug_log.call_count)
@ddt.data(constants.REPLICA_STATE_IN_SYNC,
constants.REPLICA_STATE_OUT_OF_SYNC)
def test__share_replica_update_driver_exception(self, replica_state):
mock_debug_log = self.mock_object(manager.LOG, 'debug')
replica = fake_replica(replica_state=replica_state)
self.mock_object(db, 'share_replicas_get_all_by_share',
mock.Mock(return_value=[replica]))
self.mock_object(self.share_manager.db, 'share_replica_get',
mock.Mock(return_value=replica))
self.mock_object(db, 'share_server_get',
mock.Mock(return_value='fake_share_server'))
self.mock_object(self.share_manager.driver, 'update_replica_state',
mock.Mock(side_effect=exception.ManilaException))
mock_db_update_call = self.mock_object(
self.share_manager.db, 'share_replica_update')
self.assertRaises(exception.ManilaException,
self.share_manager._share_replica_update,
self.context, replica, share_id=replica['share_id'])
self.assertFalse(mock_db_update_call.called)
self.assertEqual(1, mock_debug_log.call_count)
def test__share_replica_update_driver_exception_ignored(self):
mock_debug_log = self.mock_object(manager.LOG, 'debug')
replica = fake_replica(replica_state=constants.STATUS_ERROR)
self.mock_object(db, 'share_replicas_get_all_by_share',
mock.Mock(return_value=[replica]))
self.mock_object(self.share_manager.db, 'share_replica_get',
mock.Mock(return_value=replica))
self.mock_object(db, 'share_server_get',
@ -1150,6 +1182,8 @@ class ShareManagerTestCase(test.TestCase):
replica = fake_replica(replica_state=random.choice(replica_states),
share_server='fake_share_server')
del replica['availability_zone']
self.mock_object(db, 'share_replicas_get_all_by_share',
mock.Mock(return_value=[replica]))
self.mock_object(db, 'share_server_get',
mock.Mock(return_value='fake_share_server'))
mock_db_update_calls = []
@ -1169,10 +1203,38 @@ class ShareManagerTestCase(test.TestCase):
elif retval:
self.assertEqual(0, mock_warning_log.call_count)
mock_driver_call.assert_called_once_with(
self.context, replica, [], 'fake_share_server')
self.context, [replica], replica, [],
share_server='fake_share_server')
mock_db_update_call.assert_has_calls(mock_db_update_calls)
self.assertEqual(1, mock_debug_log.call_count)
def test_update_share_replica_replica_not_found(self):
replica = fake_replica()
self.mock_object(
self.share_manager.db, 'share_replica_get', mock.Mock(
side_effect=exception.ShareReplicaNotFound(replica_id='fake')))
self.mock_object(self.share_manager, '_get_share_server')
driver_call = self.mock_object(
self.share_manager, '_share_replica_update')
self.assertRaises(
exception.ShareReplicaNotFound,
self.share_manager.update_share_replica,
self.context, replica, share_id=replica['share_id'])
self.assertFalse(driver_call.called)
def test_update_share_replica_replica(self):
replica_update_call = self.mock_object(
self.share_manager, '_share_replica_update')
self.mock_object(self.share_manager.db, 'share_replica_get')
retval = self.share_manager.update_share_replica(
self.context, 'fake_replica_id', share_id='fake_share_id')
self.assertIsNone(retval)
self.assertTrue(replica_update_call.called)
def test_create_delete_share_snapshot(self):
"""Test share's snapshot can be created and deleted."""

View File

@ -44,6 +44,7 @@ class ShareRpcAPITestCase(test.TestCase):
share_replica = db_utils.create_share_replica(
id='fake_replica',
share_id='fake_share_id',
host='fake_host',
)
share_server = db_utils.create_share_server()
cg = {'id': 'fake_cg_id', 'host': 'fake_host'}
@ -104,6 +105,7 @@ class ShareRpcAPITestCase(test.TestCase):
if 'share_replica' in expected_msg:
share_replica = expected_msg.pop('share_replica', None)
expected_msg['share_replica_id'] = share_replica['id']
expected_msg['share_id'] = share_replica['share_id']
if 'host' in kwargs:
host = kwargs['host']
@ -113,6 +115,8 @@ class ShareRpcAPITestCase(test.TestCase):
host = kwargs['share_instance']['host']
elif 'share_server' in kwargs:
host = kwargs['share_server']['host']
elif 'share_replica' in kwargs:
host = kwargs['share_replica']['host']
else:
host = kwargs['share']['host']
target['server'] = host
@ -261,18 +265,20 @@ class ShareRpcAPITestCase(test.TestCase):
self._test_share_api('delete_share_replica',
rpc_method='cast',
version='1.8',
share_replica_id=self.fake_share_replica['id'],
share_id=self.fake_share_replica['share_id'],
force=False,
host='fake_host')
share_replica=self.fake_share_replica,
force=False)
def test_promote_share_replica(self):
self._test_share_api('promote_share_replica',
rpc_method='cast',
version='1.8',
share_replica_id=self.fake_share_replica['id'],
share_id=self.fake_share_replica['share_id'],
host='fake_host')
share_replica=self.fake_share_replica)
def test_update_share_replica(self):
self._test_share_api('update_share_replica',
rpc_method='cast',
version='1.8',
share_replica=self.fake_share_replica)
class Desthost(object):
host = 'fake_host'