Chinaunix首页 | 论坛 | 博客
  • 博客访问: 10197969
  • 博文数量: 1669
  • 博客积分: 16831
  • 博客等级: 上将
  • 技术积分: 12594
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-25 07:23
个人简介

柔中带刚,刚中带柔,淫荡中富含柔和,刚猛中荡漾风骚,无坚不摧,无孔不入!

文章分类

全部博文(1669)

文章存档

2023年(4)

2022年(1)

2021年(10)

2020年(24)

2019年(4)

2018年(19)

2017年(66)

2016年(60)

2015年(49)

2014年(201)

2013年(221)

2012年(638)

2011年(372)

分类: 系统运维

2017-08-06 11:03:34

 

openstack源码解析之虚机创建
http://blog.csdn.net/weixin_39400463/article/details/76407950

标签: 
 29人阅读 评论(0)  举报
 分类:
openstack(2) 

首先先看图,请求从nova-api发起,然后到nova-conductor,再到scheduler进行调度,调度选中某台机器后,通过rpc请求,发送到某台机器上执行创建机器方法,期间会访问glance获取镜像生成磁盘文件,也会访问neutron获取网络相关信息,最后调用libvirt,生成虚机,后面会逐个通过源码给大家讲解。




 





nova-api

  创建虚机,这里从nova层面开始分析。通过http请求,带着参数访问到nova-api。

   nova/api//compute/servers.py


[html] view plain copy
  1. def create(self, req, body):  
  2.     if body and 'servers' in body:  
  3.         context = req.environ['nova.context']  
  4.         servers = body['servers']  
  5.         return self.create_servers(context, req, servers)  


    顺着create_server方法进去,进到_create这个方法中,这个方法会获取参数信息,比如机器名字,套餐等,做一些基本验证,并且    调用compute_api的create方法


[html] view plain copy
  1. def _create(self, context, body, password):  
  2.             return self.compute_api.create(context,..  

   compute_api指的是nova.compute.api.API

   nova/compute/api.py


[html] view plain copy
  1. def create(self, context, instance_type,  
  2.                image_href, kernel_id=Noneramdisk_id=None,  
  3.                min_count=Nonemax_count=None,  
  4.                display_name=Nonedisplay_description=None,  
  5.                key_name=Nonekey_data=Nonesecurity_group=None,  
  6.                availability_zone=Noneuser_data=Nonemetadata=None,  
  7.                injected_files=Noneadmin_password=None,  
  8.                block_device_mapping=Noneaccess_ip_v4=None,  
  9.                access_ip_v6=Nonerequested_networks=Noneconfig_drive=None,  
  10.                auto_disk_config=Nonescheduler_hints=Nonelegacy_bdm=True):  
  11.                ...  
  12.                return self._create_instance(  
  13.                                context, instance_type,  
  14.                                image_href, kernel_id, ramdisk_id,  
  15.                                min_count, max_count,  
  16.                                display_name, display_description,  
  17.                                key_name, key_data, security_group,  
  18.                                availability_zone, user_data, metadata,  
  19.                                injected_files, admin_password,  
  20.                                access_ip_v4, access_ip_v6,  
  21.                                requested_networks, config_drive,  
  22.                                block_device_mapping, auto_disk_config,  
  23.                                scheduler_hints=scheduler_hints,  
  24.                                legacy_bdm=legacy_bdm)  
   继续跟进到_create_instance方法里面,会做一系列参数验证和封装,进而插入数据库instance的记录,然后调用rpc请求



[html] view plain copy
  1. def _create_instance(self, context, instance_type,  
  2.               image_href, kernel_id, ramdisk_id,  
  3.               min_count, max_count,  
  4.               display_name, display_description,  
  5.               key_name, key_data, security_groups,  
  6.               availability_zone, user_data, metadata,  
  7.               injected_files, admin_password,  
  8.               access_ip_v4, access_ip_v6,  
  9.               requested_networks, config_drive,  
  10.               block_device_mapping, auto_disk_config,  
  11.               reservation_id=Nonescheduler_hints=None,  
  12.               legacy_bdm=True):  
  13.               ...  
  14.               for instance in instances:  
  15.                 self._record_action_start(context, instance,  
  16.                                      instance_actions.CREATE)  
  17.               self.compute_task_api.build_instances...  
这个请求会跑到nova/conductor/rpcapi.py中的
[html] 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.         image_p = jsonutils.to_primitive(image)  
  5.         cctxt = self.client.prepare(version='1.5')  
  6.         cctxt.cast(context, 'build_instances',  
  7.                    instances=instancesimage=image_p,  
  8.                    filter_properties=filter_properties,  
  9.                    admin_password=admin_password,  
  10.                    injected_files=injected_files,  
  11.                    requested_networks=requested_networks,  
  12.                    security_groups=security_groups,  
  13.                    block_device_mapping=block_device_mapping,  
  14.                    legacy_bdm=legacy_bdm)  

这个时候发送了rpc请求,我们用的是 zmq点对点,发送到conductor节点上,进到cctxt.cast这个方法里面,看下nova/conductor/rpcapi.py这个文件


[html] view plain copy
  1. def __init__(self):  
  2.         super(ComputeTaskAPI, self).__init__()  
  3.         target = messaging.Target(topic=CONF.conductor.topic,  
  4.                                   namespace='compute_task',  
  5.                                   version='1.0')  
  6.         serializer = objects_base.NovaObjectSerializer()  
  7.         self.client = rpc.get_client(target, serializer=serializer)  

   nova-conductor

进入到nova/conductor/manager.py这个文件的build_instances方法


[html] 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.             self.scheduler_rpcapi.new_run_instance(context,  
  6.                     request_spec=request_specadmin_password=admin_password,  
  7.                     injected_files=injected_files,  
  8.                     requested_networks=requested_networksis_first_time=True,  
  9.                     filter_properties=filter_properties,  
  10.                     legacy_bdm_in_spec=legacy_bdm)  
我们这里改造了下,直接用了new_run_instance这个方法,进去再看下  nova/scheduler/rpcapi.py


[html] view plain copy
  1. def new_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.   
  5.         msg_kwargs = {'request_spec': request_spec,  
  6.                       'admin_password': admin_password,  
  7.                       'injected_files': injected_files,  
  8.                       'requested_networks': requested_networks,  
  9.                       'is_first_time': is_first_time,  
  10.                       'filter_properties': filter_properties,  
  11.                       'legacy_bdm_in_spec': legacy_bdm_in_spec}  
  12.         cctxt = self.client.prepare()  
  13.         cctxt.cast(ctxt, 'new_run_instance', **msg_kwargs)  

这个时候是发送了zmq请求到了scheduler上了,具体的发送过程,看下这个类的__init__方法即可。

    nova-scheduler

方法到了nova/scheduler/manager.py这个文件中,我们看SchedulerManager这个类的new_run_instance方法


[html] view plain copy
  1. def new_run_instance(self, context, request_spec, admin_password,  
  2.             injected_files, requested_networks, is_first_time,  
  3.             filter_properties, legacy_bdm_in_spec=True):  
  4.             ...  
  5.             return self.driver.new_schedule_run_instance(context,  
  6.                         request_spec, admin_password, injected_files,  
  7.                         requested_networks, is_first_time, filter_properties,  
  8.                         legacy_bdm_in_spec)  
这个用到了driver, 这个driver指的就是你使用的过滤器,可能是内存有限过滤,或者CPU, 或者硬盘。这块我们选的驱动是nova.scheduler.filter_scheduler.FilterScheduler,我们进到这个驱动,看下new_schedule_run_instance这个方法。nova/scheduler/filter_scheduler.py, 类FilterScheduler下的new_schedule_run_instance方法:



[html] view plain copy
  1. def new_schedule_run_instance(self, context, request_spec,  
  2.                               admin_password, injected_files,  
  3.                               requested_networks, is_first_time,  
  4.                               filter_properties, legacy_bdm_in_spec):  
  5.         ...  
  6.         try:  
  7.             self._new_schedule_run_instance(context, request_spec,  
  8.                     admin_password, injected_files,  
  9.                     requested_networks, is_first_time,  
  10.                     filter_properties, legacy_bdm_in_spec)  
  11.         ..  

这里说说下host_queue, 这个是定时加载的,默认时间是10s, 在nova/scheduler/manager.py中


[html] view plain copy
  1. @periodic_task.periodic_task(spacing=CONF.new_scheduler_build_queue_period,  
  2.                              run_immediately=True)  
  3. def build_queue(self, context):  
  4.     current = host_queue.QueueManager()  
  5.     current.init_host_queue(context)  
  6.     current.build_queue()  
看下build_queue的具体实现 nova/scheduler/host_queue.py



[html] view plain copy
  1. def build_queue(self):  
  2.     ...  
  3.     # 从数据库读取compute节点  
  4.     self.compute_nodes = db.compute_node_get_all(elevated)  
  5.     for compute in self.compute_nodes:  
  6.         # 获取extra_resources信息  
  7.         extra_resources = compute.get('extra_resources')  
  8.         # 获取hostname  
  9.         hostname = compute.get('hypervisor_hostname')  
  10.         # 获取queue_name, 默认是kvm  
  11.         queue_name = extra_resources.get('queue_name')  
  12.         new_queue = []  
  13.         if not queue_name:  
  14.             queue_name = CONF.default_queue  
  15.         ...  
  16.         # 过滤掉disabled的机器  
  17.         if service['disabled']:  
  18.             LOG.warn("Compute service disabled %s", hostname)  
  19.             continue  
  20.         ...  
  21.         # 获取磁盘,cpu, 内存超售比,这些值都是计算节点通过定时任务,汇报自己配置文件信息到数据库中,具体的方法就是resource_tracker  
  22.         disk_allocation_ratio = extra_resources.get('disk_allocation_ratio', 1.0)  
  23.         cpu_allocation_ratio = extra_resources.get('cpu_allocation_ratio', 1.0)  
  24.         ram_allocation_ratio = extra_resources.get('ram_allocation_ratio', 1.0)  
  25.         ...  
  26.         # 获取cpu总量,使用量,空闲量  
  27.         vcpus = compute['vcpus'] * cpu_allocation_ratio  
  28.         vcpus_used = compute['vcpus_used']  
  29.         free_vcpus = vcpus - compute['vcpus_used']  
  30.         limits['vcpu'] = vcpus  
  31.   
  32.         local_gb = compute['local_gb'] * disk_allocation_ratio  
  33.         free_local_gb = local_gb - \  
  34.                         (compute['local_gb'] - compute['free_disk_gb'])  
  35.         limits['disk_gb'] = local_gb  
  36.   
  37.         # memory_mb  
  38.         memory_mb = compute['memory_mb'] * ram_allocation_ratio  
  39.         free_memory_mb = memory_mb - \  
  40.                         (compute['memory_mb'] - compute['free_ram_mb'])  
  41.         limits['memory_mb'] = memory_mb  
  42.         ...  
  43.         # 生成对象值,放入QueueManager.host_info中  
  44.         QueueManager.host_info[hostname] = BaseQueue(  
  45.                      hostname=hostname,  
  46.                      vcpus=vcpusvcpus_used=vcpus_usedfree_vcpus=free_vcpus,  
  47.                      memory_mb=memory_mb,  
  48.                      free_memory_mb=free_memory_mblocal_gb=local_gb,  
  49.                      free_local_gb=free_local_gbnet_bandwidth=net_bandwidth,  
  50.                      net_bandwidth_used=net_bandwidth_used,  
  51.                      free_net_bandwidth=free_net_bandwidth,  
  52.                      disk_bandwidth=disk_bandwidth,  
  53.                      disk_bandwidth_used=disk_bandwidth_used,  
  54.                      free_disk_bandwidth=free_disk_bandwidth,  
  55.                      multi_disk_info=multi_disk_info,  
  56.                      updated_at=updated_atqueue_name=queue_name,  
  57.                      limits=limits)  

我们再回过头继续看调度这块,既然host_queue都有了,我们继续往下看。nova/scheduler/filter_scheduler.py



[html] view plain copy
  1. def _new_schedule_run_instance(self, context, request_spec,  
  2.                               admin_password, injected_files,  
  3.                               requested_networks, is_first_time,  
  4.                               filter_properties, legacy_bdm_in_spec):  
  5.         ## 获取参数  
  6.         ..  
  7.         ## 这里参数中如果指定了scheduler_host,直接调度到指定物理机中去创建机器。  
  8.         if scheduler_host:  
  9.             self.schedule_instance_to_assigned_host(context, request_spec,  
  10.             admin_password, injected_files,  
  11.             requested_networks, is_first_time,  
  12.             filter_properties, legacy_bdm_in_spec,  
  13.             scheduler_host, disk_shares,  
  14.             instance_uuids, scheduler_hints)  
  15.          return  
  16.          ..  
  17.          ## 默认的queue_name叫kvm, 获取队列名字下的机器,这个是在host_queue文件初始化的时候构建的。  
  18.          host_queue = self.get_host_queue(queue_name)  
  19.   
  20.          # 如果有值,这个用的是正则匹配,匹配机器名字中含有scheduler_host_match值的机器  
  21.          if scheduler_host_match:  
  22.                 host_queue = self._get_matched_host_queue(host_queue, scheduler_host_match)  
  23.                 LOG.debug("matched host queue (%s): %s length is: %d", scheduler_host_match,  
  24.                         queue_name, len(host_queue))  
  25.           ...  
  26.           # 这里设置了一个值,requested_disk值就是虚机根分区的大小,加上用户分区,再加上swap空间大小,这个在后面比对会用上  
  27.           req_res['requested_disk'] = 1024 * (instance_type['root_gb'] +  
  28.                         instance_type['ephemeral_gb']) + \  
  29.                         instance_type['swap']  
  30.           # 这个方法就是直接调度获取到匹配传递的参数的机器,这个在下面的方法中讲解  
  31.           host = self._new_schedule(context, host_queue,  
  32.                             req_res, request_spec,  
  33.                             copy_filter_properties,  
  34.                             instance_uuid, retry,  
  35.                             different_host_flag,  
  36.                             different_host, disk_shares,  
  37.                             try_different_host, sign, boundary_host)  
  38.   
  39.           # 获取到机器了,这个时候就继续发送点对点请求,给对应的机器,去创建虚拟机  
  40.           self.pool.spawn(self.compute_rpcapi.new_run_instance,  
  41.                             context, instance_uuid, host.hostname,  
  42.                             request_spec, copy_filter_properties,  
  43.                             requested_networks, injected_files,  
  44.                             admin_password, is_first_time,  
  45.                             host.hostname, legacy_bdm_in_spec, self._disk_info)  
我们继续来看_new_scheduler, 还是在这个文件中


[html] view plain copy
  1. def _new_schedule(self, context, host_queue, req_res,  
  2.         request_spec, filter_properties,  
  3.         instance_uuid, retry=None,  
  4.         different_host_flag=None,  
  5.         different_host=None,  
  6.         disk_shares=None,  
  7.         try_different_host=None,  
  8.         sign=1,  
  9.         boundary_host=None):  
  10.         ..  
  11.         # 这个含义是,如果设置了different_host为true, 则虚机的调度,要调度到不同的物理机上。  
  12.           这里的实现是通过check_host_different_from_uuids方法,每次选中的host放到数组里面,  
  13.           然后下一次选中的host, 验证下是否在这个数组里面。  
  14.         if different_host:  
  15.             LOG.debug('instance %s different_host: %s', instance_uuid,  
  16.                 different_host)  
  17.             if not self.check_host_different_from_uuids(context,  
  18.                 instance_uuid, host, different_host):  
  19.                 self._find_pos = self._find_pos + sign * 1  
  20.                 continue  
  21.         # 这里查看资源是否充足  
  22.         resource_check = self.check_host_resource(context,  
  23.         host=host,  
  24.         req_res=req_res,  
  25.         disk_shares=disk_shares)  
  26.         # 如果匹配,返回host  

我们继续深入方法里面,看下check_host_resource方法做了什么(依然还在这个文件中)


[html] view plain copy
  1. def check_host_resource(self, context, host, req_res,  
  2.                     disk_shares=0):  
  3.         ...  
  4.         # 检查要申请的磁盘空间是否比物理机上空闲的磁盘大,如果大,就返回False, 告知check不通过  
  5.         usable_disk_mb = host.free_local_gb * 1024  
  6.         if not usable_disk_mb >= req_res['requested_disk']:  
  7.             return False  
  8.   
  9.         # check 内存  
  10.         if req_res['requested_ram'] > 0:  
  11.             usable_ram = host.free_memory_mb  
  12.             if not usable_ram >= req_res['requested_ram']:  
  13.                 return False  
  14.   
  15.         # check vcpus  
  16.         if req_res['requested_vcpus'] > 0:  
  17.             if host.free_vcpus < req_res['requested_vcpus']:  
  18.                 return False  
  19.         return True  

    nova-compute

通过rpc调用到对应的host节点,执行new_run_instance方法


[html] view plain copy
  1. def new_run_instance(self, context, instance_uuid, request_spec,  
  2.                  filter_properties, requested_networks,  
  3.                  injected_files, admin_password,  
  4.                  is_first_time, node, legacy_bdm_in_spec,  
  5.                  disk_info=None):  
  6.   
  7.   
  8.     # 一方面更新数据库状态,另外一方面,更新资源使用量  
  9.     if disk_info:  
  10.         instance = self._instance_update(  
  11.                     context, instance_uuid,  
  12.                     disk_shares=disk_info['disk_shares'],  
  13.                     selected_dir=disk_info['selected_dir'])  
  14.     else:  
  15.         instance = self._instance_update(context,  
  16.                 instance_uuid)  
  17.   
  18.   
  19.     self.run_instance(context, instance, request_spec,  
  20.                 filter_properties, requested_networks, injected_files,  
  21.                 admin_password, is_first_time, node,  
  22.                 legacy_bdm_in_spec)<span style=" font-size: 24px;"><strong>  
  23. strong>span>  
继续往下看,进入run_instance方法



[html] view plain copy
  1. def _run_instance(self, context, request_spec,  
  2.                   filter_properties, requested_networks, injected_files,  
  3.                   admin_password, is_first_time, node, instance,  
  4.                   legacy_bdm_in_spec):  
  5.   
  6.       # 首先检查下机器名字是否存在,然后更新下数据库,改成building状态  
  7.       self._prebuild_instance(context, instance)  
  8.       
  9.       ...  
  10.       instance, network_info = self._build_instance(context,  
  11.               request_spec, filter_properties, requested_networks,  
  12.               injected_files, admin_password, is_first_time, node,  
  13.               instance, image_meta, legacy_bdm_in_spec)  

这个时候,进入方法里面,通过neutron服务获取mac和IP信息(这块就不细说了),直接看代码


[html] view plain copy
  1. def _build_instance(self, context, request_spec, filter_properties,  
  2.             requested_networks, injected_files, admin_password, is_first_time,  
  3.             node, instance, image_meta, legacy_bdm_in_spec):  
  4.     ..  
  5.     # 查询这个实例上挂了多少盘  
  6.     bdms = block_device_obj.BlockDeviceMappingList.get_by_instance_uuid(  
  7.         context, instance['uuid'])  
  8.     ..  
  9.     # 更新资源使用量,cpu 内存 硬盘  
  10.     with rt.instance_claim(context, instance, limits):  
  11.       ...  
  12.       # neutron将会为VM分配MAC和IP  
  13.       network_info = self._allocate_network(context, instance,  
  14.                         requested_networks, macs, security_groups,  
  15.                         dhcp_options)  
  16.   
  17.      instance = self._spawn(context, instance, image_meta,  
  18.                                        network_info, block_device_info,  
  19.                                        injected_files, admin_password,  
  20.                                        set_access_ip=set_access_ip)  
spawn方法是相对比较底层的,里面涉及镜像和创建虚机



[html] view plain copy
  1. def spawn(self, context, instance, image_meta, injected_files,  
  2.           admin_password, network_info=Noneblock_device_info=None):  
  3.     disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,  
  4.                                         instance,  
  5.                                         block_device_info,  
  6.                                         image_meta)  
  7.     # 需要注入的文件内容,最后会以字符串的形式写入injected_files中  
  8.     if CONF.libvirt.inject_nifcfg_file:  
  9.         self._mk_inject_files(image_meta, network_info, injected_files)  
  10.       
  11.     # 创建磁盘镜像文件 disk disk.local等  
  12.     self._create_image(context, instance,  
  13.                        disk_info['mapping'],  
  14.                        network_info=network_info,  
  15.                        block_device_info=block_device_info,  
  16.                        files=injected_files,  
  17.                        admin_pass=admin_password)  
  18.     # 生成libvirt.xml文件  
  19.     xml = self.to_xml(context, instance, network_info,  
  20.                       disk_info, image_meta,  
  21.                       block_device_info=block_device_info,  
  22.                       write_to_disk=True)  
  23.   
  24.     # 创建真正的虚机实例domain  
  25.     self._create_domain_and_network(context, xml, instance, network_info,  
  26.                                     block_device_info)  
  27.   
  28.     LOG.debug(_("Instance is running"), instance=instance)  
  29.   
  30.     # 监控状态是否ok, ok的话返回  
  31.     def _wait_for_boot():  
  32.         """Called at an interval until the VM is running."""  
  33.         state = self.get_info(instance)['state']  
  34.   
  35.         if state == power_state.RUNNING:  
  36.             LOG.info(_("Instance spawned successfully."),  
  37.                      instance=instance)  
  38.             raise loopingcall.LoopingCallDone()  
  39.   
  40.     timer = loopingcall.FixedIntervalLoopingCall(_wait_for_boot)  
  41.     timer.start(interval=0.5).wait()  
至此整个流程讲完了,有很多细的地方没有讲到,后续会在源码分析的其他章节讲解。




阅读(1499) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~