Cinder创建卷的请求如下:openstack volume create <name> --size <size>
对应的restapi请求:
POST /v2/{project_id}/volumes
{
"volume": {
"size": 10,
"availability_zone": null,
"source_volid": null,
"description": null,
"multiattach ": false,
"snapshot_id": null,
"backup_id": null,
"name": null,
"imageRef": null,
"volume_type": null,
"metadata": {},
"consistencygroup_id": null
},
"OS-SCH-HNT:scheduler_hints": {
"same_host": [
"a0cf03a5-d921-4877-bb5c-86d26cf818e1",
"8c19174f-4220-44f0-824a-cd1eeef10287"
]
}
}
发送该请求后,对应处理的代码入口:cinder/api/v2/volumes.py
def create(self, req, body):
"""Creates a new volume."""
// 校验请求body格式是否正确,保证body中必须有volume这个key,并且对应的value也是一个字典.
self.assert_valid_body(body, 'volume')
LOG.debug('Create volume request body: %s', body)
context = req.environ['cinder.context']
volume = body['volume']
# Check up front for legacy replication parameters to quick fail
source_replica = volume.get('source_replica')
if source_replica:
msg = _("Creating a volume from a replica source was part of the "
"replication v1 implementation which is no longer "
"available.")
raise exception.InvalidInput(reason=msg)
kwargs = {}
// 校验名称和描述,字符长度在0-255
self.validate_name_and_description(volume)
# NOTE(thingee): v2 API allows name instead of display_name
// volume的名称
if 'name' in volume:
volume['display_name'] = volume.pop('name')
# NOTE(thingee): v2 API allows description instead of
# display_description
// volume的描述信息
if 'description' in volume:
volume['display_description'] = volume.pop('description')
// 镜像ID,支持从镜像创建云硬盘
if 'image_id' in volume:
volume['imageRef'] = volume.pop('image_id')
// 获取volumetype,支持创建云硬盘时指定volumetype
req_volume_type = volume.get('volume_type', None)
if req_volume_type:
# Not found exception will be handled at the wsgi level
kwargs['volume_type'] = (
objects.VolumeType.get_by_name_or_id(context, req_volume_type))
kwargs['metadata'] = volume.get('metadata', None)
// 快照ID,支持从快照创建云硬盘
snapshot_id = volume.get('snapshot_id')
if snapshot_id is not None:
if not uuidutils.is_uuid_like(snapshot_id):
msg = _("Snapshot ID must be in UUID form.")
raise exc.HTTPBadRequest(explanation=msg)
# Not found exception will be handled at the wsgi level
kwargs['snapshot'] = self.volume_api.get_snapshot(context,
snapshot_id)
else:
kwargs['snapshot'] = None
// 源云硬盘ID,支持从云硬盘创建云硬盘
source_volid = volume.get('source_volid')
if source_volid is not None:
if not uuidutils.is_uuid_like(source_volid):
msg = _("Source volume ID '%s' must be a "
"valid UUID.") % source_volid
raise exc.HTTPBadRequest(explanation=msg)
# Not found exception will be handled at the wsgi level
kwargs['source_volume'] = \
self.volume_api.get_volume(context,
source_volid)
else:
kwargs['source_volume'] = None
// 待分析
kwargs['group'] = None
kwargs['consistencygroup'] = None
consistencygroup_id = volume.get('consistencygroup_id')
if consistencygroup_id is not None:
if not uuidutils.is_uuid_like(consistencygroup_id):
msg = _("Consistency group ID '%s' must be a "
"valid UUID.") % consistencygroup_id
raise exc.HTTPBadRequest(explanation=msg)
# Not found exception will be handled at the wsgi level
kwargs['group'] = self.group_api.get(context, consistencygroup_id)
// 云硬盘大小,如果是通过快照或者云硬盘创建,则可以不指定size,这时size直接获取快照或者云硬盘的大小
size = volume.get('size', None)
if size is None and kwargs['snapshot'] is not None:
size = kwargs['snapshot']['volume_size']
elif size is None and kwargs['source_volume'] is not None:
size = kwargs['source_volume']['size']
LOG.info("Create volume of %s GB", size)
// 如果加载了os-image-create插件的话,则获取imageid
if self.ext_mgr.is_loaded('os-image-create'):
image_ref = volume.get('imageRef')
if image_ref is not None:
image_uuid = self._image_uuid_from_ref(image_ref, context)
kwargs['image_id'] = image_uuid
kwargs['availability_zone'] = volume.get('availability_zone', None)
kwargs['scheduler_hints'] = volume.get('scheduler_hints', None)
kwargs['multiattach'] = utils.get_bool_param('multiattach', volume)
// 调用self.volume_api.create方法
new_volume = self.volume_api.create(context,
size,
volume.get('display_name'),
volume.get('display_description'),
**kwargs)
retval = self._view_builder.detail(req, new_volume)
return retval
调用self.volume_api.create方法对应:cinder/volume/api.py
def create(self, context, size, name, description, snapshot=None,
image_id=None, volume_type=None, metadata=None,
availability_zone=None, source_volume=None,
scheduler_hints=None,
source_replica=None, consistencygroup=None,
cgsnapshot=None, multiattach=False, source_cg=None,
group=None, group_snapshot=None, source_group=None,
backup=None):
context.authorize(vol_policy.CREATE_FROM_IMAGE_POLICY)
// 如下过程主要是做一些基本参数的校验,代码省略,比如
// size不能<=0
// 如果通过云硬盘创建并且指定了volumetype,则判断源云硬盘的volumetype是否和指定的一样
...
# Determine the valid availability zones that the volume could be
# created in (a task in the flow will/can use this information to
# created in (a task in the flow will/can use this information to
# ensure that the availability zone requested is valid).
// 获取可用的az
// 从数据库service表中获取topic=cinder-volume并且disable=0的zone
raw_zones = self.list_availability_zones(enable_cache=True)
availability_zones = set([az['name'] for az in raw_zones])
if CONF.storage_availability_zone:
availability_zones.add(CONF.storage_availability_zone)
// 检查metadata格式,主要判断是否是字典,metadata中的字符长度校验
utils.check_metadata_properties(metadata)
create_what = {
'context': context,
'raw_size': size,
'name': name,
'description': description,
'snapshot': snapshot,
'image_id': image_id,
'raw_volume_type': volume_type,
'metadata': metadata or {},
'raw_availability_zone': availability_zone,
'source_volume': source_volume,
'scheduler_hints': scheduler_hints,
'key_manager': self.key_manager,
'optional_args': {'is_quota_committed': False},
'consistencygroup': consistencygroup,
'cgsnapshot': cgsnapshot,
'multiattach': multiattach,
'group': group,
'group_snapshot': group_snapshot,
'source_group': source_group,
'backup': backup,
}
try:
sched_rpcapi = (self.scheduler_rpcapi if (
not cgsnapshot and not source_cg and
not group_snapshot and not source_group)
else None)
volume_rpcapi = (self.volume_rpcapi if (
not cgsnapshot and not source_cg and
not group_snapshot and not source_group)
else None)
// 非常核心!!,构建工作任务流并运行任务
flow_engine = create_volume.get_flow(self.db,
self.image_service,
availability_zones,
create_what,
sched_rpcapi,
volume_rpcapi)
except Exception:
msg = _('Failed to create api volume flow.')
LOG.exception(msg)
raise exception.CinderException(msg)
# Attaching this listener will capture all of the notifications that
# taskflow sends out and redirect them to a more useful log for
# cinders debugging (or error reporting) usage.
with flow_utils.DynamicLogListener(flow_engine, logger=LOG):
try:
flow_engine.run()
vref = flow_engine.storage.fetch('volume')
# NOTE(tommylikehu): If the target az is not hit,
# refresh the az cache immediately.
if flow_engine.storage.fetch('refresh_az'):
self.list_availability_zones(enable_cache=True,
refresh_cache=True)
# Refresh the object here, otherwise things ain't right
vref = objects.Volume.get_by_id(
context, vref['id'])
LOG.info("Create volume request issued successfully.",
resource=vref)
return vref
except exception.InvalidAvailabilityZone:
with excutils.save_and_reraise_exception():
self.list_availability_zones(enable_cache=True,
refresh_cache=True)
构建创建volume的工作任务流并运行任务的代码:cinder/volume/flows/api/create_volume.py
在该代码中使用了taskflow.engines用来管理任务
def get_flow(db_api, image_service_api, availability_zones, create_what,
scheduler_rpcapi=None, volume_rpcapi=None):
"""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.
6. Casts to volume manager or scheduler for further processing.
"""
flow_name = ACTION.replace(":", "_") + "_api"
api_flow = linear_flow.Flow(flow_name)
// 构建了如下几个任务,依次是:
// ExtractVolumeRequestTask 构造请求
// QuotaReserveTask 预留配额
// QuotaCommitTask 提交配额
// VolumeCastTask 创建volume
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(),
QuotaCommitTask())
if scheduler_rpcapi and volume_rpcapi:
# This will cast it out to either the scheduler or volume manager via
# 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)