天天看點

Openstack liberty源碼分析 之 雲主機的啟動過程1

接觸Openstack也有一段時間了,因為工作需要着重閱讀了Glance、Nova、Cinder子產品源碼并通過搭建的devstack測試環境調試學習相關操作的執行流程。現準備陸續将相關的學習成果和心得在部落格中分享出來,希望對讀者有所幫助。這是第一篇 - 雲主機啟動過程源碼分析 - 介紹雲主機啟動時,接口調用在nova-api、nova-conductor、nova-sechduler及nova-compute等元件上的流轉過程。

既可以通過Dashboard啟動雲主機,也可以通過Nova CLI指令行實作,下面的指令啟動了一台類型為2(

--flavor 2

)名為vm2的雲主機,使用的鏡像是:

226bc6e5-60d7-4a2c-bf0d-a568a1e26e00

--debug

參數用打開調試資訊:

nova-api

啟動雲主機過程中,

nova-api

主要完成輸入參數的驗證和裝配,之後根據輸入參數建立雲主機執行個體對象、記錄資料庫條目,最後将請求轉發給

nova-conductor

進行後續處理。簡單分析如下:

根據

nova-api

啟動過程中建立的路由映射以及調試資訊的佐證,如下:

Openstack liberty源碼分析 之 雲主機的啟動過程1

我們知道啟動雲主機的請求經過

novaclient

的處理後,發送給了nova-api,并由

nova/api/openstack/servers.py.ServersController.create

處理,從代碼上看該方法的處理過程比較簡單,主要是完成:使用者參數的轉換、policy驗證,方法聲明如下:

@wsgi.response()
@extensions.expected_errors((, , , ))
@validation.schema(schema_server_create_v2, '2.0', '2.0')
@validation.schema(schema_server_create, '2.1')
def create(self,req,body):
           

來看看body參數的内容:

Openstack liberty源碼分析 之 雲主機的啟動過程1

body參數是該次請求的請求體,顯示的是待啟動雲主機的配置參數,包含有:雲主機名、

image uuid

,雲主機類型等,很明顯這就是之前在

nova boot

指令行中指定的參數,之後根據

extension

擴充方法轉換使用者參數,完成參數解析,policy認證等,之後将請求轉發給

nova/compute/api.py.API.create

API.create

方法雖然參數很多,但啟動雲主機必須的參數隻有

instance_type

image_href

,分别指定主機類型及image uuid,處理邏輯也非常的簡單:執行網絡及塊裝置policy檢查(如果有),接着将參數原封不動的傳遞給

_create_instance

方法,該方法主要完成如下工作:

  • 驗證輸入參數
  • 擷取鏡像元資訊
  • 生成主機配置并建立主機執行個體對象
  • 在資料庫生成主機資訊

下面具體來看看該方法的實作(說明見紅色注釋部分):

//nova/compute/api.py.API
    def _create_instance(self, context, instance_type,
               image_href, kernel_id, ramdisk_id,
               min_count, max_count,
               display_name, display_description,
               key_name, key_data, security_groups,
               availability_zone, user_data, metadata,
               injected_files, admin_password,
               access_ip_v4, access_ip_v6,
               requested_networks, config_drive,
               block_device_mapping, auto_disk_config,
               reservation_id=None, scheduler_hints=None,
               legacy_bdm=True, shutdown_terminate=False,
               check_server_group_quota=False):
        """Verify all the input parameters regardless of the provisioning
        strategy being performed and schedule the instance(s) for
        creation.
        """

        # Normalize and setup some parameters
        if reservation_id is None:
            reservation_id = utils.generate_uid('r')
        security_groups = security_groups or ['default']
        min_count = min_count or 
        max_count = max_count or min_count
        block_device_mapping = block_device_mapping or []
        if not instance_type:
            instance_type = flavors.get_default_flavor()

        #根據uuid擷取image的metadata資訊
        if image_href:
            image_id, boot_meta = self._get_image(context, image_href)
        else:
            image_id = None
            boot_meta = self._get_bdm_image_metadata(
                context, block_device_mapping, legacy_bdm)

        self._check_auto_disk_config(image=boot_meta,
                                     auto_disk_config=auto_disk_config)

        handle_az = self._handle_availability_zone
        availability_zone, forced_host, forced_node = handle_az(context,
                                                            availability_zone)

        if not self.skip_policy_check and (forced_host or forced_node):
            check_policy(context, 'create:forced_host', {})

        #根據輸入參數,生成主機配置
        base_options, max_net_count = self._validate_and_build_base_options(
                context,
                instance_type, boot_meta, image_href, image_id, kernel_id,
                ramdisk_id, display_name, display_description,
                key_name, key_data, security_groups, availability_zone,
                forced_host, user_data, metadata, access_ip_v4,
                access_ip_v6, requested_networks, config_drive,
                auto_disk_config, reservation_id, max_count)

        # max_net_count is the maximum number of instances requested by the
        # user adjusted for any network quota constraints, including
        # consideration of connections to each requested network
        if max_net_count == :
            raise exception.PortLimitExceeded()
        elif max_net_count < max_count:
            LOG.debug("max count reduced from %(max_count)d to "
                      "%(max_net_count)d due to network port quota",
                      {'max_count': max_count,
                       'max_net_count': max_net_count})
            max_count = max_net_count

        #歸總使用者輸入(--block-device-mapping參數)、鏡像屬性中指定、flavor配置中指定的塊裝置映射
        block_device_mapping = self._check_and_transform_bdm(context,
            base_options, instance_type, boot_meta, min_count, max_count,
            block_device_mapping, legacy_bdm)

        # We can't do this check earlier because we need bdms from all sources
        # to have been merged in order to get the root bdm.
        self._checks_for_create_and_rebuild(context, image_id, boot_meta,
                instance_type, metadata, injected_files,
                block_device_mapping.root_bdm())

        instance_group = self._get_requested_instance_group(context,
                                   scheduler_hints, check_server_group_quota)

        #建立雲主機執行個體對象,并生成資料庫條目
        instances = self._provision_instances(context, instance_type,
                min_count, max_count, base_options, boot_meta, security_groups,
                block_device_mapping, shutdown_terminate,
                instance_group, check_server_group_quota)

        #scheduler需要用的過濾選項
        filter_properties = self._build_filter_properties(context,
                scheduler_hints, forced_host,
                forced_node, instance_type)

        #更新資料庫中執行個體的啟動狀态
        for instance in instances:
            self._record_action_start(context, instance,
                                      instance_actions.CREATE)

        #調用conductor api,通過conductor rpc将請求轉發給conductor manager
        self.compute_task_api.build_instances(context,
                instances=instances, image=boot_meta,
                filter_properties=filter_properties,
                admin_password=admin_password,
                injected_files=injected_files,
                requested_networks=requested_networks,
                security_groups=security_groups,
                block_device_mapping=block_device_mapping,
                legacy_bdm=False)

        return (instances, reservation_id)
           

接着上面的分析,

nova/conductor/api.py.ComputeTaskAPI.build_instance

直接将請求轉發給

nova/conductor/rpcapi.py.ComputeTaskAPI.build_instance

,來看看該函數的實作(請根據注解閱讀):

//nova/conductor/rpcapi.py.ComputeTaskAPI
def build_instances(self, context, instances, image, 
    filter_properties,
    admin_password, injected_files, requested_networks,
    security_groups, block_device_mapping, legacy_bdm=True):
    '''
    該函數的邏輯清晰簡單:根據rpc client的版本,調整雲主機參數:主機過濾參數,網絡參數,塊裝置映射參數,最後通過一個異步rpc調用,将請求轉發給conductor-manager
    '''
    image_p = jsonutils.to_primitive(image)
    version = '1.10'
    if not self.client.can_send_version(version):
        version = '1.9'
        if 'instance_type' in filter_properties:
             flavor = filter_properties['instance_type']
             flavor_p = objects_base.obj_to_primitive(flavor)
             filter_properties = dict(filter_properties,
                                        instance_type=flavor_p)
        kw = {'instances': instances, 'image': image_p,
               'filter_properties': filter_properties,
               'admin_password': admin_password,
               'injected_files': injected_files,
               'requested_networks': requested_networks,
               'security_groups': security_groups}
   if not self.client.can_send_version(version):
       version = '1.8'
       kw['requested_networks'] = 
       kw['requested_networks'].as_tuples()
   if not self.client.can_send_version('1.7'):
       version = '1.5'
       bdm_p = 
       objects_base.obj_to_primitive(block_device_mapping)
       kw.update({'block_device_mapping': bdm_p,
                       'legacy_bdm': legacy_bdm})

    #傳回_CallContext對象
    cctxt = self.client.prepare(version=version)
    #發送異步rpc消息到消息隊列,conductor-manager會收到該消息
    cctxt.cast(context, 'build_instances', **kw)
           

至此,

nova-api

的任務就完成了,下一篇文章分析conductor-manage的處理過程。

繼續閱讀