Openstack Cinder源码分析-创建Volume(一)

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)

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值