Openstack Cinderでvolumeプロセスを確立するソースコード解析(4)----およびtaskflow関連解析


友达がこのブログを支持することに感谢して、共に交流を探求することを歓迎して、能力と时间が限られているため、间违ったところは避けられないで、指摘を歓迎します!転載する場合は、作者情報を保持してください.ブログアドレス:http://blog.csdn.net/gaoxingnengjisuan メールアドレス:[email protected]
前回のブログではcinderモジュールでクライアントから送信されたリクエスト情報操作を実現する主なステップがすべて解析済みである.このブログでは、cinder.api.v 1.volumes.VolumeController.createを解析し、cinderがボリュームの構築をどのように実現しているかをさらに解析します.さらに、このセクションでは、taskflowライブラリ(または設計モードと理解できる)を適用してボリュームの構築プロセスを実現する.
現在openstackのソースコードはモジュールと機能の増加と完備に伴い、コード量が急速に増加し、機能の実現複雑度も増加し、taskflowライブラリを導入することで便利なコード管理を実現し、機能実現の安全性を増加することができる.簡単に言えば、1つの機能を実現する場合、taskflowモードを適用することでflowの管理を実現することができ、あるflow操作を実行して異常が発生した場合、具体的な状況に応じてflowの逆転操作を試み、flow実行前の状態に遡ることができる.
このブログでは、ボリュームの構築を例に、cinderモジュールにおけるtaskflowのボリューム構築過程における応用状況を具体的に解析します.
コード/cinder/api/v 1/volumes.py----class VolumeController----def createの具体的なソースコード実装を見てみましょう.
    @wsgi.serializers(xml=VolumeTemplate)
    @wsgi.deserializers(xml=CreateDeserializer)
    def create(self, req, body):
        """
        Creates a new volume.
             API;
        
        req = POST /v1/ecf0109bda814fa1a548af63f9ada370/volumes HTTP/1.0
              Accept: application/json
              Accept-Encoding: gzip, deflate, compress
              Content-Length: 294
              Content-Type: application/json
              Host: 172.21.5.164:8776
              User-Agent: python-cinderclient
              X-Auth-Project-Id: admin
              X-Auth-Token: MIIQKQYJKoZI......TBvg==
              X-Domain-Id: None
              X-Domain-Name: None
              X-Identity-Status: Confirmed
              X-Project-Domain-Id: None
              X-Project-Domain-Name: None
              X-Project-Id: ecf0109bda814fa1a548af63f9ada370
              X-Project-Name: admin
              X-Role: _member_,admin
              X-Roles: _member_,admin
              X-Service-Catalog: 
              [{"endpoints_links": [], "endpoints": [....], "type": "compute", "name": "nova"}, 
               {"endpoints_links": [], "endpoints": [....], "type": "network", "name": "neutron"}, 
               {"endpoints_links": [], "endpoints": [....], "type": "volumev2", "name": "cinder_v2"}, 
               {"endpoints_links": [], "endpoints": [....], "type": "s3", "name": "swift_s3"}, 
               {"endpoints_links": [], "endpoints": [....], "type": "image", "name": "glance"}, 
               {"endpoints_links": [], "endpoints": [....], "type": "metering", "name": "ceilometer"}, 
               {"endpoints_links": [], "endpoints": [....], "type": "volume", "name": "cinder"}, 
               {"endpoints_links": [], "endpoints": [....], "type": "ec2", "name": "nova_ec2"}, 
               {"endpoints_links": [], "endpoints": [....], "type": "object-store", "name": "swift"}, 
               {"endpoints_links": [], "endpoints": [....], "type": "identity", "name": "keystone"}]
              X-Tenant: admin
              X-Tenant-Id: ecf0109bda814fa1a548af63f9ada370
              X-Tenant-Name: admin
              X-User: admin
              X-User-Domain-Id: None
              X-User-Domain-Name: None
              X-User-Id: d2ee2dd06c9e49098a3d2a278c650b6c
              X-User-Name: admin
              {"volume": {"status": "creating", 
                          "availability_zone": null, 
                          "source_volid": null, 
                          "display_description": null, 
                          "snapshot_id": null, 
                          "user_id": null, 
                          "size": 1, 
                          "display_name": "shinian01", 
                          "imageRef": null, 
                          "attach_status": "detached", 
                          "volume_type": null, 
                          "project_id": null, 
                          "metadata": {}}}
        
        body = {u'volume': {u'status': u'creating', 
                            u'user_id': None, 
                            u'imageRef': None, 
                            u'availability_zone': None, 
                            'scheduler_hints': {}, 
                            u'attach_status': u'detached', 
                            u'display_description': None, 
                            u'metadata': {}, 
                            u'source_volid': None, 
                            u'snapshot_id': None, 
                            u'display_name': u'shinian01', 
                            u'project_id': None, 
                            u'volume_type': None, 
                            u'size': 1}}
        """        
        if not self.is_valid_body(body, 'volume'):
            raise exc.HTTPUnprocessableEntity()

        LOG.debug('Create volume request body: %s', body)
        
        #   cinder      ;
        context = req.environ['cinder.context']
        volume = body['volume']

        kwargs = {}

        #         volume_type  ;
        req_volume_type = volume.get('volume_type', None)
        #req_volume_type = None
        
        #        id         ;
        if req_volume_type:
            try:
                if not uuidutils.is_uuid_like(req_volume_type):
                    #             ;
                    kwargs['volume_type'] = volume_types.get_volume_type_by_name(context, req_volume_type)
                else:
                    #     id            ;
                    kwargs['volume_type'] = volume_types.get_volume_type(context, req_volume_type)
            except exception.VolumeTypeNotFound:
                explanation = 'Volume type not found.'
                raise exc.HTTPNotFound(explanation=explanation)

        #       body         ;
        kwargs['metadata'] = volume.get('metadata', None)
        # kwargs['metadata'] = {}

        #       body      id  ;
        snapshot_id = volume.get('snapshot_id')
        # snapshot_id = None
        
        #       id  ,   snapshot_id         。        id  ,    None;
        if snapshot_id is not None:
            # get_snapshot:  snapshot_id      ;
            kwargs['snapshot'] = self.volume_api.get_snapshot(context, snapshot_id)
        else:
            kwargs['snapshot'] = None

        #       body    source_volid  ;
        source_volid = volume.get('source_volid')
        # source_volid = None
        
        #        id  ,   source_volid       。         id  ,    None;
        if source_volid is not None:
            #   volume_id  volume;
            kwargs['source_volume'] = self.volume_api.get_volume(context, source_volid)
        else:
            kwargs['source_volume'] = None

        #       body    size  ;
        size = volume.get('size', None)
        # size = 1
        
        #   size None,  kwargs['snapshot']   kwargs['source_volume']        ;
        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.audit(_("Create volume of %s GB"), size, context=context)

        image_href = None
        image_uuid = None
        
        #   self.extensions     'os-image-create';
        if self.ext_mgr.is_loaded('os-image-create'):
            # NOTE(jdg): misleading name "imageRef" as it's an image-id
            image_href = volume.get('imageRef')
            
            if image_href:
                #  image_href  uuid;
                image_uuid = self._image_uuid_from_href(image_href)
                kwargs['image_id'] = image_uuid

        #       body    availability_zone  ;
        kwargs['availability_zone'] = volume.get('availability_zone', None)
        # kwargs['availability_zone'] = None

        new_volume = self.volume_api.create(context,
                                            size,
                                            volume.get('display_name'), #        ;
                                            volume.get('display_description'),
                                            **kwargs)

        # TODO(vish): Instance should be None at db layer instead of
        #             trying to lazy load, but for now we turn it into
        #             a dict to avoid an error.
        #   new_volume  ;
        new_volume = dict(new_volume.iteritems())

        self._add_visible_admin_metadata(context, new_volume)

        # _translate_volume_detail_view:              ;
        retval = _translate_volume_detail_view(context, new_volume, image_uuid)

        return {'volume': retval}

この方法はよく理解され、主に3つの部分を完成しました.
1.reqとbodyによってボリュームの作成に必要な関連パラメータを取得する.ボリュームの作成方法はいくつかあるので、どのボリュームの作成方法を採用するかは、reqとbodyの関連パラメータによって決定される.また、ボリュームの作成には、ボリュームの大きさなど、いくつかのパラメータを決定する必要があるかもしれない.
2.呼び出し方法createはボリュームの確立を実現する.
3.ボリュームを作成した後のフィードバック情報を取得し、フォーマット変換を実現し、その中の重要な属性情報を取得し、ボリュームを生成した作成した応答情報を上位層に呼び出す.
ここで呼び出されるメソッドcreateを見てみましょう.実際に呼び出されるのはメソッド/cinder/volume/api.py----class API----def createです.具体的なソースコードの実装を見てみましょう.
    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, backup_source_volume=None):
        """
                ;
        """

        def check_volume_az_zone(availability_zone):
            """
              availability_zone      (        zone    );
            """
            try:
                # _valid_availabilty_zone:  availability_zone      (        zone    );
                return self._valid_availabilty_zone(availability_zone)
            except exception.CinderException:
                LOG.exception(_("Unable to query if %s is in the "
                                "availability zone set"), availability_zone)
                return False

        #             ;
        create_what = {
            'size': size,
            'name': name,
            'description': description,
            'snapshot': snapshot,
            'image_id': image_id,
            'volume_type': volume_type,
            'metadata': metadata,
            'availability_zone': availability_zone,
            'source_volume': source_volume,
            'scheduler_hints': scheduler_hints,
            'key_manager': self.key_manager,
            'backup_source_volume': backup_source_volume,
        }
        
        #            flow;
        # self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI();
        # self.volume_rpcapi = volume_rpcapi.VolumeAPI();
        # self.image_service = (image_service or glance.get_default_image_service())
        # check_volume_az_zone:  availability_zone      (        zone    );
        # create_what:            ;
        (flow, uuid) = create_volume.get_api_flow(self.scheduler_rpcapi,
                                                  self.volume_rpcapi,
                                                  self.db,
                                                  self.image_service,
                                                  check_volume_az_zone,
                                                  create_what)

        #   assert      flow   ;
        assert flow, _('Create volume flow not retrieved')
        
        
        #         flow;
        flow.run(context)
        
        #   flow       states.SUCCESS,     ;
        if flow.state != states.SUCCESS:
            raise exception.CinderException(_("Failed to successfully complete"
                                              " create volume workflow"))

        # Extract the volume information from the task uuid that was specified
        # to produce said information.
        #   task uuid         ;
        volume = None
        try:
            volume = flow.results[uuid]['volume']
        except KeyError:
            pass

        # Raise an error, nobody provided it??
        #   assert      volume   ;
        assert volume, _('Expected volume result not found')
        
        return volume

この方法では主に4つの部分が完成しています.
1.辞書作成_whatは、ボリュームの特定のパラメータを統合することを実現します.
2.ボリュームを構築して戻すflow;
3.ボリュームを作成するために構築されたflowを実行する.
4.flowからボリュームの作成に関するフィードバック情報を取得する.
1.文変数create_を見てみましょうwhatの出力例:
    create_what = {'backup_source_volume': None,                    'description': None,                    'availability_zone': None,                    'image_id': None,                    'scheduler_hints': None,                    'name': u'shinian01',                    'source_volume': None,                    'volume_type': None,                    'snapshot': None,                    'size': 1,                    'key_manager': ,                    'metadata': {}}
2.ボリュームの作成に使用するflowを構築して戻す
ボリュームを構築するためのflowをどのように構築するかに重点を置いてみましょう.
文を見てみましょう.
(flow, uuid) = create_volume.get_api_flow(self.scheduler_rpcapi,                                           self.volume_rpcapi,                                           self.db,                                           self.image_service,                                           check_volume_az_zone,                                           create_what)
まず、転送方法getを簡単に分析します.api_flowの変数情報:
self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI();
このパラメータは、要求情報にボリュームを構築するターゲットホストが指定されていない場合、スケジューラAPIの方法によって、特定の状況に応じて適切なスケジューリングアルゴリズムを適用して、ターゲットホストを選択し、ボリュームを構築する操作を実現するために使用される.
self.volume_rpcapi = volume_rpcapi.VolumeAPI();
このパラメータはvolume RPC APIクライアントを指定し、RPCによるボリュームのリモート関連操作の実行を実現する.
self.image_service = (image_service or glance.get_default_image_service());
このパラメータは、主にミラー実装ボリュームの確立の場合に使用されるミラーサービスのインタフェースAPIを指定する.
check_volume_az_zone:availabilityの検証_zoneが使用可能かどうか(すなわち、使用可能なzoneのリストに含まれるかどうか).
create_what:作成するボリュームの仕様データ情報;
OK!メソッドget_を見てみましょうapi_flow、この方法は主にボリュームを構築して返すflowを実現し、そのソースコードの実現を見てみましょう.
def get_api_flow(scheduler_rpcapi, volume_rpcapi, db,
                 image_service,
                 az_check_functor,
                 create_what):
    """
               flow;
    flow        :
    1.   Flow      ;
    2.      create_what flow ;
    3.             ,           ;
    4.                ,           ,         ;
                       ,                   ;
    5.                ;
    6.                  ;
    7.            ;
    """

    #   flow     ;
    # ACTION = 'volume:create'
    # flow_name = 'volume_create_api'
    flow_name = ACTION.replace(":", "_") + "_api"
    
    #    Flow      ;
    # Flow:        ;
    api_flow = linear_flow.Flow(flow_name)
    
    #        task flow;
    # InjectTask:            create_what flow ;
    # create_what             (  );
    api_flow.add(base.InjectTask(create_what, addons=[ACTION]))
    
    #        task flow;
    # ExtractVolumeRequestTask:              ,            ,      task  ;
    #   task               ,                    ;
    #                     ,               ;
    #                 ,            task 。
    # image_service:          
    # az_check_functor:  availability_zone      (        zone    );
    api_flow.add(ExtractVolumeRequestTask(image_service, az_check_functor))
    
    #        task flow;
    # QuotaReserveTask:                         ;
    #                ,           ,         ;
    #                ,                ;
    #                  ,               ,                   ;
    # QuotaReserveTask:                         ;
    api_flow.add(QuotaReserveTask())
    
    #        task flow;
    # EntryCreateTask:                ;
    v_uuid = api_flow.add(EntryCreateTask(db))
    
    #        task flow;
    # QuotaCommitTask:                  ;
    #(       ,           ,     flow      );
    api_flow.add(QuotaCommitTask())

    #        task flow;
    #   task         ,    id      ERROR;
    api_flow.add(OnFailureChangeStatusTask(db))

    #        task flow;
    #             ;
    api_flow.add(VolumeCastTask(scheduler_rpcapi, volume_rpcapi, db))


    # Note(harlowja): this will return the flow as well as the uuid of the
    # task which will produce the 'volume' database reference (since said
    # reference is returned to other callers in the api for further usage).
    #            ,   flow task uuid ;
    return (flow_utils.attach_debug_listeners(api_flow), v_uuid)

taskflowの構築に関する脈絡が明確であることを見ることができ,この方法では主に3つの部分によってボリュームを構築するためのflowの構築を実現した.
1.Flowクラスを初期化する.
2.関連するtaskをflowに順番に追加する.
3.taskを追加したflowを呼び出し者に返す.
OK!このブログはまずここまで書きますが、次のブログでは、ボリュームの作成に使用するflowにどのようなtaskが追加されているのかに注目します.もちろん、taskflowの応用はまだ分析が完了していないので、後で分析とまとめを続けます.