Tripleo之nova-compute 和Ironic的代碼深入分析(一)

聲明:

本博客歡迎轉載,但請保留原作者信息!

作者:姜飛

團隊:華爲杭州OpenStack團隊


我們大家都知道部署物理機跟部署虛擬機的概念在nova來看是一樣,都是nova通過創建虛擬機的方式來觸發,只是底層nova-scheduler和nova-compute的驅動不一樣。虛擬機的底層驅動採用的libvirt的虛擬化技術,而物理機是採用PXE+IPMItool的方式,當讓PXE+ipmitool只是其中一種比較常用的技術,其他的方式也可以實現,像開發環境部比較少的話,也可以使用PXE+SSH的方式,我就是採用這種方式實踐的。

採用官方的一個圖,可以大致瞭解他的框架和流程是怎麼走的,詳情請見官方文檔:http://docs.openstack.org/developer/ironic/deploy/user-guide.html



好了,廢話少說,那我們就一起來看看相關的代碼分析:

 

配置

在/etc/nova/nova.conf的配置文件中修改manager和driver

Vi /etc/nova/nova.conf

[DEFAULT]
scheduler_host_manager = nova.scheduler.ironic_host_manager.IronicHostManager
compute_driver = nova.virt.ironic.driver.IronicDriver
compute_manager = ironic.nova.compute.manager.ClusteredComputeManager
[ironic]
admin_username = ironic
admin_password = unset
admin_url = http://127.0.0.1:35357/v2.0
admin_tenant_name = service

nova-scheduler的mamager是:
scheduler_host_manager = nova.scheduler.ironic_host_manager.IronicHostManager

nova-compute的manager和driver是:

compute_driver =nova.virt.ironic.driver.IronicDriver

compute_manager = ironic.nova.compute.manager.ClusteredComputeManager

 

其中compute_manager的代碼實現是在ironic項目裏面。 

創建虛擬機的流程就不描述了,nova-api接收到nova boot的請求,nova-scheduler收到請求後,在scheduler_host_manager裏面處理:

Nova-scheduler會使用flavor裏面的額外屬性extra_specs,像cpu_arch,baremetal:deploy_kernel_id,baremetal:deploy_ramdisk_id等過濾條件找到相匹配的物理節點。


Nova-compute

Nova-compute在啓動的時候會初始化nova.service.py的Service類,調用launch方法將nova-compute的進程拉起來,最終調用到Service類的start方法:

    def start(self):
        verstr = version.version_string_with_package()
        LOG.audit(_('Starting %(topic)s node (version %(version)s)'),
                  {'topic': self.topic, 'version': verstr})
        self.basic_config_check()
        self.manager.init_host()  # compute-manager的初始化
        self.model_disconnected = False
        ctxt = context.get_admin_context()
		…….
		#compute_manager的pre_start_hook方法
        self.manager.pre_start_hook()
        …..

那麼這個self.manager是哪裏定義的呢,

nova.compute的main函數:

   server = service.Service.create(binary='nova-compute',

                                   topic=CONF.compute_topic,

                                   db_allowed=CONF.conductor.use_local)

nova.service的create方法:

    def create(cls, host=None, binary=None, topic=None, manager=None,
               report_interval=None, periodic_enable=None,
               periodic_fuzzy_delay=None, periodic_interval_max=None,
               db_allowed=True):
        #這裏的binary就是nova-compute,manager_cls就是compute-manger,manager就是獲取的compute-manager的配置項。
        if not manager:
            manager_cls = ('%s_manager' %
                           binary.rpartition('nova-')[2]) 
            manager = CONF.get(manager_cls, None)
			……
        service_obj = cls(host, binary, topic, manager,
                          report_interval=report_interval,
                          periodic_enable=periodic_enable,
                          periodic_fuzzy_delay=periodic_fuzzy_delay,
                          periodic_interval_max=periodic_interval_max,
                          db_allowed=db_allowed)
而self.manager的初始化是在Services的初始化方法:

def __init__(self, host, binary, topic, manager, report_interval=None,
                 periodic_enable=None, periodic_fuzzy_delay=None,
                 periodic_interval_max=None, db_allowed=True,
                 *args, **kwargs):
        super(Service, self).__init__()
        self.host = host
        self.binary = binary
        self.topic = topic
        self.manager_class_name = manager
        self.servicegroup_api = servicegroup.API(db_allowed=db_allowed)
        manager_class = importutils.import_class(self.manager_class_name)
        self.manager = manager_class(host=self.host, *args, **kwargs)

self.manager.init_host()調用的是compute-manager的init-host方法,我們配置文件裏面寫的是ironic.nova.compute.manager.ClusteredComputeManager, 所以調用到了compute-manager的init-host方法。

class ClusteredComputeManager(manager.ComputeManager):

    def init_host(self):
        """Initialization for a clustered compute service."""
        #調用compute_driver的init_host方法,當前沒有做什麼事情
        self.driver.init_host(host=self.host) 
      	
        #調用父類的init_virt_events方法
        self.init_virt_events()

compute-manager的init-host方法的第一步調用self.driver.init_host(host=self.host),這裏的self.dirver是什麼呢? 其實就是我們在配置文件裏面寫的compute-driver。

 

我們來看下self.dirver是怎麼定義的:

ClusteredComputeManager的父類是nova.compute.manager.ComputeManager的初始化方法__init__方法:
def __init__(self, compute_driver=None, *args, **kwargs):
        """Load configuration options and connect to the hypervisor."""
        self.virtapi = ComputeVirtAPI(self)
        self.network_api = network.API()
        self.volume_api = volume.API()
        self.image_api = image.API()
        self._last_host_check = 0
		…….
        self.driver = driver.load_compute_driver(self.virtapi, compute_driver)
而load_compute_driver做的事情就是加載相關的driver

def load_compute_driver(virtapi, compute_driver=None):
    if not compute_driver:
    	compute_driver = CONF.compute_driver

    if not compute_driver:
        LOG.error(_("Compute driver option required, but not specified"))
        sys.exit(1)

    LOG.info(_("Loading compute driver '%s'") % compute_driver)
    try:
        driver = importutils.import_object_ns('nova.virt',
                                              compute_driver,
                                              virtapi)
        return utils.check_isinstance(driver, ComputeDriver)
    except ImportError:
        LOG.exception(_("Unable to load the virtualization driver"))
        sys.exit(1)

細心的同學應該注意到,爲什麼self.manager = manager_class(host=self.host, *args, **kwargs)調用的是host=self.host,到了ClusteredComputeManager的初始化方法卻是compute_driver=None, 其實這個就是可選參數, 在load_compute_driver方法裏面,假如compute_driver爲None,則設置compute_driver= CONF.compute_driver,所以self.driver就是compute_driver的配置項設置的值。

OK,回到剛纔self.manager的init_host方法,self.driver.init_host(host=self.host)調用的是nova.virt.ironic.driver.IronicDriver的init_host方法,當前Juno版本的init_host方法啥事情都沒有做。父類的init_virt_events方法調用slef.driver的register_event_listener方法,但是self.driver沒實現這個方法,所以需要到dirvier的父virt_driver.ComputeDriver,將self.handle_events作爲self._compute_event_callback的回調函數,這個回調函數是接收從libvirtd的異步事件消息。

    def init_virt_events(self):
        self.driver.register_event_listener(self.handle_events)

回到Service類的start方法,往下的話就是判斷nova-compute的service是否存在,這些就省略不寫,跟nova-compute的流程是一樣的。

接着看self.manager.pre_start_hook(), 這個Ironic的manager類ClusteredComputeManager是有實現的:

    def pre_start_hook(self):
        try:
            self.update_available_resource(nova.context.get_admin_context())
        except Exception:
            pass

self.update_available_resource的是父類nova.compute.manage.ComputeManager的方法:

update_available_resource這個是個週期任務,這方法主要就是把ironic所有的node節點的信息都同步到compute_node中去,nova-scheduler在走過濾器的時候才能根據過濾條件找到適合的物理主機。

    @periodic_task.periodic_task
    def update_available_resource(self, context):
        """See driver.get_available_resource()

        Periodic process that keeps that the compute host's understanding of
        resource availability and usage in sync with the underlying hypervisor.

        :param context: security context
        """
        new_resource_tracker_dict = {}

        nodenames = set(self.driver.get_available_nodes())
        for nodename in nodenames:
            rt = self._get_resource_tracker(nodename)
            rt.update_available_resource(context)
            new_resource_tracker_dict[nodename] = rt

        # Delete orphan compute node not reported by driver but still in db
        compute_nodes_in_db = self._get_compute_nodes_in_db(context,
                                                            use_slave=True)

        for cn in compute_nodes_in_db:
            if cn.hypervisor_hostname not in nodenames:
                LOG.audit(_("Deleting orphan compute node %s") % cn.id)
                cn.destroy()

        self._resource_tracker_dict = new_resource_tracker_dict

self.driver的get_available_nodes獲取ironic的所有註冊的node節點,方法_refresh_cache()內部維護了一個self.node_cache= {}的一個dict,從ironic的api接口ironic node-list查詢出所有的node節點,放在緩存中, 如果該節點存在的話,則調用resource_tracker.ResourceTracker生成一個新的rt對象,緩存到self._resource_tracker_dict中,返回這個rt對象。然後執行rt.update_available_resource(), 在nova.compute.resource_tracker.ResourceTracker類中。

rt.update_available_resource()是先從node-cache中找到該節點信息,如果沒有,則從ironic-api中調用ironic接口查詢,比較關鍵的是最後一個動作self._node_resource(node), 這個動作會做了一些邏輯判斷,CPU類型轉化等。


    def get_available_resource(self, nodename):
        …….
        return self._node_resource(node)

    def _node_resource(self, node):
        """Helper method to create resource dict from node stats."""
        vcpus = int(node.properties.get('cpus', 0))
        memory_mb = int(node.properties.get('memory_mb', 0))
        local_gb = int(node.properties.get('local_gb', 0))
        raw_cpu_arch = node.properties.get('cpu_arch', None)
        try:
            #將amd64轉化爲X86-64,其他的類型會轉化I686,或者不存在
            cpu_arch = arch.canonicalize(raw_cpu_arch)
        except exception.InvalidArchitectureName:
            cpu_arch = None
        if not cpu_arch:
            LOG.warn(_LW("cpu_arch not defined for node '%s'"), node.uuid)

        nodes_extra_specs = {}
        nodes_extra_specs['cpu_arch'] = raw_cpu_arch
        …..
        if node.instance_uuid:
            # Node has an instance, report all resource as unavailable
            vcpus_used = vcpus
            memory_mb_used = memory_mb
            local_gb_used = local_gb
        elif self._node_resources_unavailable(node):
        #假如單板的power-state在[ironic_states.ERROR, ironic_states.NOSTATE] 或者屬於維護狀態,則不上報該單板的資源。
            vcpus = 0
            memory_mb = 0
            local_gb = 0
		……
        dic.update(nodes_extra_specs)
        return dic

這裏跟虛擬機的driver不同主要是在hypervisor_type 是ironic ,cpu_info是” baremetal cpu”,其他地方都是大同小異,沒有什麼不同。


Nova-compute走完self.manager.pre_start_hook()這個步驟,後面的步驟跟虛擬機的啓動完全都是一樣的了,創建rabbitmq的消息隊列等等,這裏就不在描述,詳見nova-compute的流程,tripleo就不在這裏描述了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章