Merge "Refactor API create_volume flow"
This commit is contained in:
commit
06e5b315ef
@ -277,18 +277,14 @@ class API(base.Base):
|
|||||||
'multiattach': multiattach,
|
'multiattach': multiattach,
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
if cgsnapshot:
|
sched_rpcapi = self.scheduler_rpcapi if not cgsnapshot else None
|
||||||
flow_engine = create_volume.get_flow_no_rpc(self.db,
|
volume_rpcapi = self.volume_rpcapi if not cgsnapshot else None
|
||||||
self.image_service,
|
flow_engine = create_volume.get_flow(self.db,
|
||||||
availability_zones,
|
self.image_service,
|
||||||
create_what)
|
availability_zones,
|
||||||
else:
|
create_what,
|
||||||
flow_engine = create_volume.get_flow(self.scheduler_rpcapi,
|
sched_rpcapi,
|
||||||
self.volume_rpcapi,
|
volume_rpcapi)
|
||||||
self.db,
|
|
||||||
self.image_service,
|
|
||||||
availability_zones,
|
|
||||||
create_what)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
msg = _('Failed to create api volume flow.')
|
msg = _('Failed to create api volume flow.')
|
||||||
LOG.exception(msg)
|
LOG.exception(msg)
|
||||||
|
@ -72,117 +72,64 @@ class ExtractVolumeRequestTask(flow_utils.CinderTask):
|
|||||||
self.availability_zones = availability_zones
|
self.availability_zones = availability_zones
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_consistencygroup(consistencygroup):
|
def _extract_resource(resource, allowed_vals, exc, resource_name,
|
||||||
"""Extracts the consistencygroup id from the provided consistencygroup.
|
props=('status',)):
|
||||||
|
"""Extracts the resource id from the provided resource.
|
||||||
|
|
||||||
This function validates the input consistencygroup dict and checks that
|
This method validates the input resource dict and checks that the
|
||||||
the status of that consistencygroup is valid for creating a volume in.
|
properties which names are passed in `props` argument match
|
||||||
|
corresponding lists in `allowed` argument. In case of mismatch
|
||||||
|
exception of type exc is raised.
|
||||||
|
|
||||||
|
:param resource: Resource dict.
|
||||||
|
:param allowed_vals: Tuple of allowed values lists.
|
||||||
|
:param exc: Exception type to raise.
|
||||||
|
:param resource_name: Name of resource - used to construct log message.
|
||||||
|
:param props: Tuple of resource properties names to validate.
|
||||||
|
:return: Id of a resource.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
consistencygroup_id = None
|
resource_id = None
|
||||||
if consistencygroup is not None:
|
if resource:
|
||||||
if consistencygroup['status'] not in CG_PROCEED_STATUS:
|
for prop, allowed_states in zip(props, allowed_vals):
|
||||||
msg = _("Originating consistencygroup status must be one"
|
if resource[prop] not in allowed_states:
|
||||||
" of '%s' values")
|
msg = _("Originating %(res)s %(prop)s must be one of"
|
||||||
msg = msg % (", ".join(CG_PROCEED_STATUS))
|
"'%(vals)s' values")
|
||||||
raise exception.InvalidConsistencyGroup(reason=msg)
|
msg = msg % {'res': resource_name,
|
||||||
consistencygroup_id = consistencygroup['id']
|
'prop': prop,
|
||||||
return consistencygroup_id
|
'vals': ', '.join(allowed_states)}
|
||||||
|
# TODO(harlowja): what happens if the status changes after
|
||||||
|
# this initial resource status check occurs??? Seems like
|
||||||
|
# someone could delete the resource after this check passes
|
||||||
|
# but before the volume is officially created?
|
||||||
|
raise exc(reason=msg)
|
||||||
|
resource_id = resource['id']
|
||||||
|
return resource_id
|
||||||
|
|
||||||
@staticmethod
|
def _extract_consistencygroup(self, consistencygroup):
|
||||||
def _extract_cgsnapshot(cgsnapshot):
|
return self._extract_resource(consistencygroup, (CG_PROCEED_STATUS,),
|
||||||
"""Extracts the cgsnapshot id from the provided cgsnapshot.
|
exception.InvalidConsistencyGroup,
|
||||||
|
'consistencygroup')
|
||||||
|
|
||||||
This function validates the input cgsnapshot dict and checks that
|
def _extract_cgsnapshot(self, cgsnapshot):
|
||||||
the status of that cgsnapshot is valid for creating a cg from.
|
return self._extract_resource(cgsnapshot, (CGSNAPSHOT_PROCEED_STATUS,),
|
||||||
"""
|
exception.InvalidCgSnapshot,
|
||||||
|
'CGSNAPSHOT')
|
||||||
|
|
||||||
cgsnapshot_id = None
|
def _extract_snapshot(self, snapshot):
|
||||||
if cgsnapshot:
|
return self._extract_resource(snapshot, (SNAPSHOT_PROCEED_STATUS,),
|
||||||
if cgsnapshot['status'] not in CGSNAPSHOT_PROCEED_STATUS:
|
exception.InvalidSnapshot, 'snapshot')
|
||||||
msg = _("Originating CGSNAPSHOT status must be one"
|
|
||||||
" of '%s' values")
|
|
||||||
msg = msg % (", ".join(CGSNAPSHOT_PROCEED_STATUS))
|
|
||||||
raise exception.InvalidCgSnapshot(reason=msg)
|
|
||||||
cgsnapshot_id = cgsnapshot['id']
|
|
||||||
return cgsnapshot_id
|
|
||||||
|
|
||||||
@staticmethod
|
def _extract_source_volume(self, source_volume):
|
||||||
def _extract_snapshot(snapshot):
|
return self._extract_resource(source_volume, (SRC_VOL_PROCEED_STATUS,),
|
||||||
"""Extracts the snapshot id from the provided snapshot (if provided).
|
exception.InvalidVolume, 'source volume')
|
||||||
|
|
||||||
This function validates the input snapshot dict and checks that the
|
def _extract_source_replica(self, source_replica):
|
||||||
status of that snapshot is valid for creating a volume from.
|
return self._extract_resource(source_replica, (SRC_VOL_PROCEED_STATUS,
|
||||||
"""
|
REPLICA_PROCEED_STATUS),
|
||||||
|
exception.InvalidVolume,
|
||||||
snapshot_id = None
|
'replica', ('status',
|
||||||
if snapshot is not None:
|
'replication_status'))
|
||||||
if snapshot['status'] not in SNAPSHOT_PROCEED_STATUS:
|
|
||||||
msg = _("Originating snapshot status must be one"
|
|
||||||
" of %s values")
|
|
||||||
msg = msg % (", ".join(SNAPSHOT_PROCEED_STATUS))
|
|
||||||
# TODO(harlowja): what happens if the status changes after this
|
|
||||||
# initial snapshot status check occurs??? Seems like someone
|
|
||||||
# could delete the snapshot after this check passes but before
|
|
||||||
# the volume is officially created?
|
|
||||||
raise exception.InvalidSnapshot(reason=msg)
|
|
||||||
snapshot_id = snapshot['id']
|
|
||||||
return snapshot_id
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _extract_source_volume(source_volume):
|
|
||||||
"""Extracts the volume id from the provided volume (if provided).
|
|
||||||
|
|
||||||
This function validates the input source_volume dict and checks that
|
|
||||||
the status of that source_volume is valid for creating a volume from.
|
|
||||||
"""
|
|
||||||
|
|
||||||
source_volid = None
|
|
||||||
if source_volume is not None:
|
|
||||||
if source_volume['status'] not in SRC_VOL_PROCEED_STATUS:
|
|
||||||
msg = _("Unable to create a volume from an originating source"
|
|
||||||
" volume when its status is not one of %s"
|
|
||||||
" values")
|
|
||||||
msg = msg % (", ".join(SRC_VOL_PROCEED_STATUS))
|
|
||||||
# TODO(harlowja): what happens if the status changes after this
|
|
||||||
# initial volume status check occurs??? Seems like someone
|
|
||||||
# could delete the volume after this check passes but before
|
|
||||||
# the volume is officially created?
|
|
||||||
raise exception.InvalidVolume(reason=msg)
|
|
||||||
source_volid = source_volume['id']
|
|
||||||
return source_volid
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _extract_source_replica(source_replica):
|
|
||||||
"""Extracts the volume id from the provided replica (if provided).
|
|
||||||
|
|
||||||
This function validates the input replica_volume dict and checks that
|
|
||||||
the status of that replica_volume is valid for creating a volume from.
|
|
||||||
"""
|
|
||||||
|
|
||||||
source_replicaid = None
|
|
||||||
if source_replica is not None:
|
|
||||||
if source_replica['status'] not in SRC_VOL_PROCEED_STATUS:
|
|
||||||
msg = _("Unable to create a volume from an originating source"
|
|
||||||
" volume when its status is not one of %s"
|
|
||||||
" values")
|
|
||||||
msg = msg % (", ".join(SRC_VOL_PROCEED_STATUS))
|
|
||||||
# TODO(harlowja): what happens if the status changes after this
|
|
||||||
# initial volume status check occurs??? Seems like someone
|
|
||||||
# could delete the volume after this check passes but before
|
|
||||||
# the volume is officially created?
|
|
||||||
raise exception.InvalidVolume(reason=msg)
|
|
||||||
replication_status = source_replica['replication_status']
|
|
||||||
if replication_status not in REPLICA_PROCEED_STATUS:
|
|
||||||
msg = _("Unable to create a volume from a replica"
|
|
||||||
" when replication status is not one of %s"
|
|
||||||
" values")
|
|
||||||
msg = msg % (", ".join(REPLICA_PROCEED_STATUS))
|
|
||||||
# TODO(ronenkat): what happens if the replication status
|
|
||||||
# changes after this initial volume status check occurs???
|
|
||||||
raise exception.InvalidVolume(reason=msg)
|
|
||||||
source_replicaid = source_replica['id']
|
|
||||||
return source_replicaid
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_size(size, source_volume, snapshot):
|
def _extract_size(size, source_volume, snapshot):
|
||||||
@ -328,6 +275,7 @@ class ExtractVolumeRequestTask(flow_utils.CinderTask):
|
|||||||
else:
|
else:
|
||||||
# For backwards compatibility use the storage_availability_zone
|
# For backwards compatibility use the storage_availability_zone
|
||||||
availability_zone = CONF.storage_availability_zone
|
availability_zone = CONF.storage_availability_zone
|
||||||
|
|
||||||
if availability_zone not in self.availability_zones:
|
if availability_zone not in self.availability_zones:
|
||||||
msg = _("Availability zone '%s' is invalid") % (availability_zone)
|
msg = _("Availability zone '%s' is invalid") % (availability_zone)
|
||||||
LOG.warning(msg)
|
LOG.warning(msg)
|
||||||
@ -379,27 +327,22 @@ class ExtractVolumeRequestTask(flow_utils.CinderTask):
|
|||||||
return encryption_key_id
|
return encryption_key_id
|
||||||
|
|
||||||
def _get_volume_type_id(self, volume_type, source_volume, snapshot):
|
def _get_volume_type_id(self, volume_type, source_volume, snapshot):
|
||||||
volume_type_id = None
|
|
||||||
if not volume_type and source_volume:
|
if not volume_type and source_volume:
|
||||||
volume_type_id = source_volume['volume_type_id']
|
return source_volume['volume_type_id']
|
||||||
elif snapshot is not None:
|
elif snapshot is not None:
|
||||||
if volume_type:
|
if volume_type:
|
||||||
current_volume_type_id = volume_type.get('id')
|
current_volume_type_id = volume_type.get('id')
|
||||||
if (current_volume_type_id !=
|
if current_volume_type_id != snapshot['volume_type_id']:
|
||||||
snapshot['volume_type_id']):
|
|
||||||
msg = _LW("Volume type will be changed to "
|
msg = _LW("Volume type will be changed to "
|
||||||
"be the same as the source volume.")
|
"be the same as the source volume.")
|
||||||
LOG.warning(msg)
|
LOG.warning(msg)
|
||||||
volume_type_id = snapshot['volume_type_id']
|
return snapshot['volume_type_id']
|
||||||
else:
|
else:
|
||||||
volume_type_id = volume_type.get('id')
|
return volume_type.get('id')
|
||||||
|
|
||||||
return volume_type_id
|
|
||||||
|
|
||||||
def execute(self, context, size, snapshot, image_id, source_volume,
|
def execute(self, context, size, snapshot, image_id, source_volume,
|
||||||
availability_zone, volume_type, metadata,
|
availability_zone, volume_type, metadata, key_manager,
|
||||||
key_manager, source_replica,
|
source_replica, consistencygroup, cgsnapshot):
|
||||||
consistencygroup, cgsnapshot):
|
|
||||||
|
|
||||||
utils.check_exclusive_options(snapshot=snapshot,
|
utils.check_exclusive_options(snapshot=snapshot,
|
||||||
imageRef=image_id,
|
imageRef=image_id,
|
||||||
@ -425,7 +368,7 @@ class ExtractVolumeRequestTask(flow_utils.CinderTask):
|
|||||||
# their volume type matches the source volume is too convoluted. We
|
# their volume type matches the source volume is too convoluted. We
|
||||||
# should copy encryption metadata from the encrypted volume type to the
|
# should copy encryption metadata from the encrypted volume type to the
|
||||||
# volume upon creation and propagate that information to each snapshot.
|
# volume upon creation and propagate that information to each snapshot.
|
||||||
# This strategy avoid any dependency upon the encrypted volume type.
|
# This strategy avoids any dependency upon the encrypted volume type.
|
||||||
def_vol_type = volume_types.get_default_volume_type()
|
def_vol_type = volume_types.get_default_volume_type()
|
||||||
if not volume_type and not source_volume and not snapshot:
|
if not volume_type and not source_volume and not snapshot:
|
||||||
volume_type = def_vol_type
|
volume_type = def_vol_type
|
||||||
@ -532,14 +475,15 @@ class EntryCreateTask(flow_utils.CinderTask):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def revert(self, context, result, optional_args, **kwargs):
|
def revert(self, context, result, optional_args, **kwargs):
|
||||||
# We never produced a result and therefore can't destroy anything.
|
|
||||||
if isinstance(result, ft.Failure):
|
if isinstance(result, ft.Failure):
|
||||||
|
# We never produced a result and therefore can't destroy anything.
|
||||||
return
|
return
|
||||||
|
|
||||||
if optional_args['is_quota_committed']:
|
if optional_args['is_quota_committed']:
|
||||||
# Committed quota doesn't rollback as the volume has already been
|
# If quota got commited we shouldn't rollback as the volume has
|
||||||
# created at this point, and the quota has already been absorbed.
|
# already been created and the quota has already been absorbed.
|
||||||
return
|
return
|
||||||
|
|
||||||
vol_id = result['volume_id']
|
vol_id = result['volume_id']
|
||||||
try:
|
try:
|
||||||
self.db.volume_destroy(context.elevated(), vol_id)
|
self.db.volume_destroy(context.elevated(), vol_id)
|
||||||
@ -594,7 +538,7 @@ class QuotaReserveTask(flow_utils.CinderTask):
|
|||||||
usages = e.kwargs['usages']
|
usages = e.kwargs['usages']
|
||||||
|
|
||||||
def _consumed(name):
|
def _consumed(name):
|
||||||
return (usages[name]['reserved'] + usages[name]['in_use'])
|
return usages[name]['reserved'] + usages[name]['in_use']
|
||||||
|
|
||||||
def _is_over(name):
|
def _is_over(name):
|
||||||
for over in overs:
|
for over in overs:
|
||||||
@ -675,6 +619,7 @@ class QuotaCommitTask(flow_utils.CinderTask):
|
|||||||
# We never produced a result and therefore can't destroy anything.
|
# We never produced a result and therefore can't destroy anything.
|
||||||
if isinstance(result, ft.Failure):
|
if isinstance(result, ft.Failure):
|
||||||
return
|
return
|
||||||
|
|
||||||
volume = result['volume_properties']
|
volume = result['volume_properties']
|
||||||
try:
|
try:
|
||||||
reserve_opts = {'volumes': -1, 'gigabytes': -volume['size']}
|
reserve_opts = {'volumes': -1, 'gigabytes': -volume['size']}
|
||||||
@ -695,10 +640,11 @@ class QuotaCommitTask(flow_utils.CinderTask):
|
|||||||
class VolumeCastTask(flow_utils.CinderTask):
|
class VolumeCastTask(flow_utils.CinderTask):
|
||||||
"""Performs a volume create cast to the scheduler or to the volume manager.
|
"""Performs a volume create cast to the scheduler or to the volume manager.
|
||||||
|
|
||||||
This which will signal a transition of the api workflow to another child
|
This will signal a transition of the api workflow to another child and/or
|
||||||
and/or related workflow on another component.
|
related workflow on another component.
|
||||||
|
|
||||||
Reversion strategy: N/A
|
Reversion strategy: rollback source volume status and error out newly
|
||||||
|
created volume.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, scheduler_rpcapi, volume_rpcapi, db):
|
def __init__(self, scheduler_rpcapi, volume_rpcapi, db):
|
||||||
@ -718,23 +664,23 @@ class VolumeCastTask(flow_utils.CinderTask):
|
|||||||
volume_id = request_spec['volume_id']
|
volume_id = request_spec['volume_id']
|
||||||
snapshot_id = request_spec['snapshot_id']
|
snapshot_id = request_spec['snapshot_id']
|
||||||
image_id = request_spec['image_id']
|
image_id = request_spec['image_id']
|
||||||
group_id = request_spec['consistencygroup_id']
|
cgroup_id = request_spec['consistencygroup_id']
|
||||||
host = None
|
host = None
|
||||||
cgsnapshot_id = request_spec['cgsnapshot_id']
|
cgsnapshot_id = request_spec['cgsnapshot_id']
|
||||||
|
|
||||||
if group_id:
|
if cgroup_id:
|
||||||
group = self.db.consistencygroup_get(context, group_id)
|
cgroup = self.db.consistencygroup_get(context, cgroup_id)
|
||||||
if group:
|
if cgroup:
|
||||||
host = group.get('host', None)
|
host = cgroup.get('host', None)
|
||||||
elif snapshot_id and CONF.snapshot_same_host:
|
elif snapshot_id and CONF.snapshot_same_host:
|
||||||
# NOTE(Rongze Zhu): A simple solution for bug 1008866.
|
# NOTE(Rongze Zhu): A simple solution for bug 1008866.
|
||||||
#
|
#
|
||||||
# If snapshot_id is set, make the call create volume directly to
|
# If snapshot_id is set and CONF.snapshot_same_host is True, make
|
||||||
# the volume host where the snapshot resides instead of passing it
|
# the call create volume directly to the volume host where the
|
||||||
# through the scheduler. So snapshot can be copy to new volume.
|
# snapshot resides instead of passing it through the scheduler, so
|
||||||
|
# snapshot can be copied to the new volume.
|
||||||
snapshot = objects.Snapshot.get_by_id(context, snapshot_id)
|
snapshot = objects.Snapshot.get_by_id(context, snapshot_id)
|
||||||
source_volume_ref = self.db.volume_get(context,
|
source_volume_ref = self.db.volume_get(context, snapshot.volume_id)
|
||||||
snapshot.volume_id)
|
|
||||||
host = source_volume_ref['host']
|
host = source_volume_ref['host']
|
||||||
elif source_volid:
|
elif source_volid:
|
||||||
source_volume_ref = self.db.volume_get(context, source_volid)
|
source_volume_ref = self.db.volume_get(context, source_volid)
|
||||||
@ -772,7 +718,7 @@ class VolumeCastTask(flow_utils.CinderTask):
|
|||||||
image_id=image_id,
|
image_id=image_id,
|
||||||
source_volid=source_volid,
|
source_volid=source_volid,
|
||||||
source_replicaid=source_replicaid,
|
source_replicaid=source_replicaid,
|
||||||
consistencygroup_id=group_id)
|
consistencygroup_id=cgroup_id)
|
||||||
|
|
||||||
def execute(self, context, **kwargs):
|
def execute(self, context, **kwargs):
|
||||||
scheduler_hints = kwargs.pop('scheduler_hints', None)
|
scheduler_hints = kwargs.pop('scheduler_hints', None)
|
||||||
@ -797,9 +743,8 @@ class VolumeCastTask(flow_utils.CinderTask):
|
|||||||
LOG.error(_LE('Unexpected build error:'), exc_info=exc_info)
|
LOG.error(_LE('Unexpected build error:'), exc_info=exc_info)
|
||||||
|
|
||||||
|
|
||||||
def get_flow(scheduler_rpcapi, volume_rpcapi, db_api,
|
def get_flow(db_api, image_service_api, availability_zones, create_what,
|
||||||
image_service_api, availability_zones,
|
scheduler_rpcapi=None, volume_rpcapi=None):
|
||||||
create_what):
|
|
||||||
"""Constructs and returns the api entrypoint flow.
|
"""Constructs and returns the api entrypoint flow.
|
||||||
|
|
||||||
This flow will do the following:
|
This flow will do the following:
|
||||||
@ -825,39 +770,10 @@ def get_flow(scheduler_rpcapi, volume_rpcapi, db_api,
|
|||||||
EntryCreateTask(db_api),
|
EntryCreateTask(db_api),
|
||||||
QuotaCommitTask())
|
QuotaCommitTask())
|
||||||
|
|
||||||
# This will cast it out to either the scheduler or volume manager via
|
if scheduler_rpcapi and volume_rpcapi:
|
||||||
# the rpc apis provided.
|
# This will cast it out to either the scheduler or volume manager via
|
||||||
api_flow.add(VolumeCastTask(scheduler_rpcapi, volume_rpcapi, db_api))
|
# the rpc apis provided.
|
||||||
|
api_flow.add(VolumeCastTask(scheduler_rpcapi, volume_rpcapi, db_api))
|
||||||
# Now load (but do not run) the flow using the provided initial data.
|
|
||||||
return taskflow.engines.load(api_flow, store=create_what)
|
|
||||||
|
|
||||||
|
|
||||||
def get_flow_no_rpc(db_api, image_service_api, availability_zones,
|
|
||||||
create_what):
|
|
||||||
"""Constructs and returns the api entrypoint flow.
|
|
||||||
|
|
||||||
This flow will do the following:
|
|
||||||
|
|
||||||
1. Inject keys & values for dependent tasks.
|
|
||||||
2. Extracts and validates the input keys & values.
|
|
||||||
3. Reserves the quota (reverts quota on any failures).
|
|
||||||
4. Creates the database entry.
|
|
||||||
5. Commits the quota.
|
|
||||||
"""
|
|
||||||
|
|
||||||
flow_name = ACTION.replace(":", "_") + "_api"
|
|
||||||
api_flow = linear_flow.Flow(flow_name)
|
|
||||||
|
|
||||||
api_flow.add(ExtractVolumeRequestTask(
|
|
||||||
image_service_api,
|
|
||||||
availability_zones,
|
|
||||||
rebind={'size': 'raw_size',
|
|
||||||
'availability_zone': 'raw_availability_zone',
|
|
||||||
'volume_type': 'raw_volume_type'}))
|
|
||||||
api_flow.add(QuotaReserveTask(),
|
|
||||||
EntryCreateTask(db_api),
|
|
||||||
QuotaCommitTask())
|
|
||||||
|
|
||||||
# Now load (but do not run) the flow using the provided initial data.
|
# Now load (but do not run) the flow using the provided initial data.
|
||||||
return taskflow.engines.load(api_flow, store=create_what)
|
return taskflow.engines.load(api_flow, store=create_what)
|
||||||
|
Loading…
Reference in New Issue
Block a user