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:
parent
68925cbac7
commit
c288d9020a
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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'},
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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', [])
|
||||
|
|
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue