Nova创建虚拟机实例过程简述

有了前面的一些基础之后,我们再来看一下,大家可能非常关系的一个问题,就是如何通过OpenStack创建一个虚拟机实例。本文注重结构性分析,细节性问题,请小伙伴们自己多思考撒,因为我也是正在边学Python,边看nova源码的,我的理解肯定会有错误的地方,也劳烦大神们,看到错误的时候,和小弟说一下,先谢了。



1、nova-api  <文件位于/nova/api/openstackcompute/server.py>

      nova-api起到了一个Cloud Controller的作用,主要为所有的API查询提供了一个接口(比如Openstack API ,EC2 API),引发多数业务流程的活动(如运行一个实例),并实施一些政策(主要是配额检查)。

      因为今天我们主题是虚拟机实例的启动过程,所以,重点关注/nova/api/openstackcompute/server.py。找到类class Controller(wsgi.Controller):create()代码段,我们知道nova-api的作用就是对外提供标准REST接口的服务。下面的代码段,已经标注了很多内容,大家看一下即可。

[python] view plain copy
  1. #对外提供创建虚拟机实例的外部接口,nova-api.create()  
  2.     #其中req是整个http报文内容,body就是REST中传递过来的参数  
  3.     #整个方法的作用是将REST接口参数映射到内部接口compute-api.create()  
  4.     #比如非常重要的环节,将image-flavor的id转换成虚拟机具体配置信息instanc_type  
  5.     def create(self, req, body):  
  6.         """Creates a new server for a given user."""  
  7.         if not self.is_valid_body(body, 'server'):  
  8.             raise exc.HTTPUnprocessableEntity()  
  9.   
  10.         #A mapping object representing the string environment.   
  11.         #For example, environ['HOME'] is the pathname of your   
  12.         #home directory (on some platforms), and is equivalent to getenv("HOME") in C  
  13.           
  14.         #对下面两个参数的讨论,设计到webob等概念,需要在以后做专门的讲述  
  15.         #*****************经常会用到的两个参数***************  
  16.         context = req.environ['nova.context']  
  17.         server_dict = body['server']  
  18.         #*****************经常会用到的两个参数***************  


nova-api的create方法作用是将REST接口参数映射到内部接口compute-api.create(),参数转换完成后,create最后会调用如下代码,转去调用comput-api
[python] view plain copy
  1. (instances, resv_id) = self.compute_api.create(context,  
  2.                            inst_type,  ###*****已经转换的flavor  
  3.                            image_uuid,  
  4.                            display_name=name,  
  5.                            display_description=name,  
  6.                            key_name=key_name,  
  7.                            metadata=server_dict.get('metadata', {}),  
  8.                            access_ip_v4=access_ip_v4,  
  9.                            access_ip_v6=access_ip_v6,  
  10.                            injected_files=injected_files,  
  11.                            admin_password=password,  
  12.                            min_count=min_count,  
  13.                            max_count=max_count,  
  14.                            requested_networks=requested_networks,  
  15.                            security_group=sg_names,  
  16.                            user_data=user_data,  
  17.                            availability_zone=availability_zone,  
  18.                            config_drive=config_drive,  
  19.                            block_device_mapping=block_device_mapping,  
  20.                            auto_disk_config=auto_disk_config,  
  21.                            scheduler_hints=scheduler_hints,  
  22.                            legacy_bdm=legacy_bdm)  


2、compute-api 的处理过程

       compute-api的作用是对外提供了管理compute的api接口,外部模块通过这些接口完成对计算资源的操作。

       

[python] view plain copy
  1. #*********************************#compute-api下面的create()函数*******************************#  
  2.     @hooks.add_hook("create_instance")  
  3.     def create(self, context, instance_type,     #由/nova/api/compute/service.py的套餐id转换到这个type  
  4.                image_href, kernel_id=None, ramdisk_id=None,  
  5.                min_count=None, max_count=None,  
  6.                display_name=None, display_description=None,  
  7.                key_name=None, key_data=None, security_group=None,  
  8.                availability_zone=None, user_data=None, metadata=None,  
  9.                injected_files=None, admin_password=None,  
  10.                block_device_mapping=None, access_ip_v4=None,  
  11.                access_ip_v6=None, requested_networks=None, config_drive=None,  
  12.                auto_disk_config=None, scheduler_hints=None, legacy_bdm=True):  
  13.         """ 
  14.         Provision instances, sending instance information to the 
  15.         scheduler.  The scheduler will determine where the instance(s) 
  16.         go and will handle creating the DB entries. 
  17.              
  18.         Returns a tuple of (instances, reservation_id) 
  19.         """  
  20.         #policy是nova中一个资格验证机制  
  21.         self._check_create_policies(context, availability_zone,  
  22.                 requested_networks, block_device_mapping)  
  23.         #创建一个实例的函数,  
  24.         return self._create_instance(  
  25.                                context, instance_type,  
  26.                                image_href, kernel_id, ramdisk_id,  
  27.                                min_count, max_count,  
  28.                                display_name, display_description,  
  29.                                key_name, key_data, security_group,  
  30.                                availability_zone, user_data, metadata,  
  31.                                injected_files, admin_password,  
  32.                                access_ip_v4, access_ip_v6,  
  33.                                requested_networks, config_drive,  
  34.                                block_device_mapping, auto_disk_config,  
  35.                                scheduler_hints=scheduler_hints,  
  36.                                legacy_bdm=legacy_bdm)  


我们看下上面的代码:a) 首先进行了能否创建实例的资格验证  b)再调用了_create_instance()方法


3、在_create_instance()方法中,做的一些操作有,验证各种参数,如意套餐类型instance_type是否存在,租户配额限制检查等,没问题后,commit一下,确定资源的占用。为了简化问题,我大概描述一下该方法做的事情,具体小伙伴们看一下代码就知道了。

完成上面的操作后,在_create_instance() 最后调用了 self.compute_task_api.build_instances(context,***)方法


4、我们通过寻找compute_task_api,找到了这个其实conductor.ComputeTaskAPI()的一个对象。

注:关于conductor的话,我们单独还会再讲,G版中开始添加了这个conductor,以前的版本是没有这个东西的。主要作用是隔离compute对数据库的直接操作。

我们转到/nova/conductor/api.py文件下

[python] view plain copy
  1. #创建实例的调用  
  2.     def build_instances(self, context, instances, image, filter_properties,  
  3.             admin_password, injected_files, requested_networks,  
  4.             security_groups, block_device_mapping, legacy_bdm=True):  
  5.         self.conductor_compute_rpcapi.build_instances(context,  
  6.                 instances=instances, image=image,  
  7.                 filter_properties=filter_properties,  
  8.                 admin_password=admin_password, injected_files=injected_files,  
  9.                 requested_networks=requested_networks,  
  10.                 security_groups=security_groups,  
  11.                 block_device_mapping=block_device_mapping,  
  12.                 legacy_bdm=legacy_bdm)  

通过上述代码,我们看到,compute调用了conductor的API接口,再调用了conductor的RpcAPi接口,转到 /nova/conductor/rpcapi.py看如下代码:
[python] view plain copy
  1. #创建虚拟机实例  
  2.     def build_instances(self, context, instances, image, filter_properties,  
  3.             admin_password, injected_files, requested_networks,  
  4.             security_groups, block_device_mapping, legacy_bdm=True):  
  5.         instances_p = [jsonutils.to_primitive(inst) for inst in instances]  
  6.         image_p = jsonutils.to_primitive(image)  
  7.           
  8.         cctxt = self.client.prepare(version='1.5')  
  9.           
  10.         cctxt.cast(context, 'build_instances',  
  11.                    instances=instances_p, image=image_p,  
  12.                    filter_properties=filter_properties,  
  13.                    admin_password=admin_password,  
  14.                    injected_files=injected_files,  
  15.                    requested_networks=requested_networks,  
  16.                    security_groups=security_groups,  
  17.                    block_device_mapping=block_device_mapping,  
  18.                    legacy_bdm=legacy_bdm)  

哈哈,看到了吧,上面的调用给conductor发送了一个rpc消息。根据OpenStack AMQP的rpc消息传递的原理,我们很顺理成章的转到 /nova/conductor/manager.py去查看是否在manager.py里面会有"build_instance"方法。找了下,果然有,请看如下代码:
[python] view plain copy
  1. def build_instances(self, context, instances, image, filter_properties,  
  2.             admin_password, injected_files, requested_networks,  
  3.             security_groups, block_device_mapping, legacy_bdm=True):  
  4.           
  5.         request_spec = scheduler_utils.build_request_spec(context, image,  
  6.                                                           instances)      
  7.         request_spec.update({'block_device_mapping': block_device_mapping,  
  8.                              'security_group': security_groups})  
  9.           
  10.         self.scheduler_rpcapi.run_instance(context, request_spec=request_spec,  
  11.                 admin_password=admin_password, injected_files=injected_files,  
  12.                 requested_networks=requested_networks, is_first_time=True,  
  13.                 filter_properties=filter_properties,  
  14.                 legacy_bdm_in_spec=legacy_bdm)  

看到最后一行, self.scheduler_rpcapi.run_instance(context,***方法,conductor通过调用Scheduler的rpcapi接口,把请求启动虚拟机的相关信息传递到调度器上。就是从这里开始,相关的工作就被转移到了Scheduler中了( 关于具体怎么调度的,会有专门的博文,讲述这个过程,今天只关注,虚拟机实例请求是怎么一步一步建立的)


5、再次转到/nova/scheduler/目录

(1)首先看/nova/shceduler/rpcapi.py

[python] view plain copy
  1. <strong> </strong>def run_instance(self, ctxt, request_spec, admin_password,  
  2.             injected_files, requested_networks, is_first_time,  
  3.             filter_properties, legacy_bdm_in_spec=True):  
  4.         version = '2.0'  
  5.           
  6.         #制作成msg_kwargs的参数  
  7.         #消息参数(用户请求,过滤属性等等)  
  8.         msg_kwargs = {'request_spec': request_spec,  
  9.                       'admin_password': admin_password,  
  10.                       'injected_files': injected_files,  
  11.                       'requested_networks': requested_networks,  
  12.                       'is_first_time': is_first_time,  
  13.                       'filter_properties': filter_properties}  
  14.   
  15.         if self.client.can_send_version('2.9'):  
  16.             version = '2.9'  
  17.             msg_kwargs['legacy_bdm_in_spec'] = legacy_bdm_in_spec  
  18.         cctxt = self.client.prepare(version=version)  
  19.       
  20.         return cctxt.cast(ctxt, 'run_instance', **msg_kwargs)  


继续看到最后一句,Scheduler-rpcpai把消息发送到了Scheduler的manager去处理具体的run_instance()方法。

(2) 看到/nova/scheduler/manager.py

找到如下代码:

[python] view plain copy
  1. #启动虚拟机  
  2.     def run_instance(self, context, request_spec, admin_password,  
  3.             injected_files, requested_networks, is_first_time,  
  4.             filter_properties, legacy_bdm_in_spec=True):  
  5.         """Tries to call schedule_run_instance on the driver. 
  6.      
  7.         Sets instance vm_state to ERROR on exceptions 
  8.         """  
  9.         instance_uuids = request_spec['instance_uuids']  
  10.         """Compute-related Utilities and helpers."""  
  11.         #compute_utils = utils  
  12.         #EventReoprt()是一个类  
  13.         with compute_utils.EventReporter(context, conductor_api.LocalAPI(),  
  14.                                          'schedule', *instance_uuids):  
  15.             try:  
  16.                    
  17.                 #调用driver(filter_scheduler.py)中的schedule_run_instance()  
  18.                 return self.driver.schedule_run_instance(context,  
  19.                         request_spec, admin_password, injected_files,  
  20.                         requested_networks, is_first_time, filter_properties,  
  21.                         legacy_bdm_in_spec)  

上图代码中,driver通过动态读取nova.conf配置文件,我们知道 driver = Filter_Scheduler 。创建实例调度的时候,我们默认使用的是FilterScheduler类中的schedule_run_instance()方法,在该方法中,有如下调用,主要作用是通过_schedule方法,筛选出我们需要已经按照经过(Filters筛选+权重计算)的符合条件的最后weight_hosts列表。---筛选的具体过程,我们以后再看,现在暂不做讨论。

[python] view plain copy
  1. weighed_hosts = self._schedule(context, request_spec,  
  2.                                       filter_properties, instance_uuids)  


(6)通过调用_provision_resource()方法,在选定的compute节点上运行虚拟机实例。

[python] view plain copy
  1. try:  
  2.                     #默认只要第一个符合条件的主机  
  3.                     #The best_host  
  4.                     weighed_host = weighed_hosts.pop(0)  
  5.                     LOG.info(_("Choosing host %(weighed_host)s "  
  6.                                 "for instance %(instance_uuid)s"),  
  7.                               {'weighed_host': weighed_host,  
  8.                                'instance_uuid': instance_uuid})  
  9.                 except IndexError:  
  10.                     raise exception.NoValidHost(reason="")  
  11.   
  12.                 #开始分配资源  
  13.                 self._provision_resource(context, weighed_host,  
  14.                                          request_spec,  
  15.                                          filter_properties,  
  16.                                          requested_networks,  
  17.                                          injected_files, admin_password,  
  18.                                          is_first_time,  
  19.                                          instance_uuid=instance_uuid,  
  20.                                          legacy_bdm_in_spec=legacy_bdm_in_spec)  


Scheduler通过一系列操作选定了最后的compute节点之后,必然要讲控制权,转移到compute中,所以,又要开始一次rpc调用了,如下面代码所述:
[python] view plain copy
  1. #*************调度完成,开始使用compute-Manager的run_instance()******************#  
  2.             self.compute_rpcapi.run_instance(context,  
  3.                     instance=updated_instance,  
  4.                     host=weighed_host.obj.host,  
  5.                     request_spec=request_spec,  
  6.                     filter_properties=filter_properties,  
  7.                     requested_networks=requested_networks,  
  8.                     injected_files=injected_files,  
  9.                     admin_password=admin_password, is_first_time=is_first_time,  
  10.                     node=weighed_host.obj.nodename,  
  11.                     legacy_bdm_in_spec=legacy_bdm_in_spec)  
  12. #*************调度完成,开始使用compute-Manager的run_instance()********************#  

(7) 控制权转移,我们需要看/nova/compute目录了,因为前面已经讲过,rpc过来的消息,最后会经过compute-manager处理,所以,我们直接看 /nova/compute/compute.py下面的run_instance()方法,代码如下:
[python] view plain copy
  1. def run_instance(self, context, instance, request_spec=None,  
  2.                      filter_properties=None, requested_networks=None,  
  3.                      injected_files=None, admin_password=None,  
  4.                      is_first_time=False, node=None, legacy_bdm_in_spec=True):  
  5.   
  6.         if filter_properties is None:  
  7.             filter_properties = {}  
  8.  
  9.         @utils.synchronized(instance['uuid'])  
  10.         def do_run_instance():  
  11.             self._run_instance(context, request_spec,  
  12.                     filter_properties, requested_networks, injected_files,  
  13.                     admin_password, is_first_time, node, instance,  
  14.                     legacy_bdm_in_spec)  
  15.         do_run_instance()  

至此,再经过compute内部一些操作,一个实例就创建完成了,具体细节不再讨论。

你可能感兴趣的:(Nova创建虚拟机实例过程简述)