openstack虚拟机启动过程源码分析
源码版本:H版
以nova-api为起点开始分析!
一、在nova-api进程中进行处理
根据对nova api的分析,当请求发过来的时候,由相应的Controller进行处理,此处如下:
nova/api/openstack/compute/servers.py
Controller类: @wsgi.response(202) @wsgi.serializers(xml=FullServerTemplate) @wsgi.deserializers(xml=CreateDeserializer) def create(self, req, body): try: _get_inst_type = flavors.get_flavor_by_flavor_id inst_type = _get_inst_type(flavor_id, ctxt=context, read_deleted="no") (instances, resv_id) = self.compute_api.create(context, ... legacy_bdm=legacy_bdm) ...
这里需要发现self.compute_api究竟为哪个类的对象。从Controller类的__init__函数中发现:
"""compute指nova.compute""" self.compute_api = compute.API()
nova/compute/__init__.py
def API(*args, **kwargs): importutils = nova.openstack.common.importutils """此处class_name为nova.compute.api.API""" class_name = _get_compute_api_class_name() """创建nova.compute.api.API类对象并返回""" return importutils.import_object(class_name, *args, **kwargs)
所以self.compute_api即为nova.compute.api.API类的对象。
1、接着看self.compute_api的create函数调用
nova/compute/api.py
API类: @hooks.add_hook("create_instance") def create(self, context, instance_type, ...): ... return self._create_instance( ...)
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): ... 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)
这里需要分析self.compute_task_api为哪个类的对象,代码分析如下:
nova/compute/api.py
API类: @property def compute_task_api(self): if self._compute_task_api is None: from nova import conductor self._compute_task_api = conductor.ComputeTaskAPI() return self._compute_task_api
nova/conductor/__init__.py
def ComputeTaskAPI(*args, **kwargs): use_local = kwargs.pop(\'use_local\', False) if oslo.config.cfg.CONF.conductor.use_local or use_local: api = conductor_api.LocalComputeTaskAPI else: """conductor_api为nova.conductor.api""" api = conductor_api.ComputeTaskAPI return api(*args, **kwargs)
所以self.compute_task_api为nova.conductor.api.ComputeTaskAPI类的对象。
2、接着看self.compute_task_api的build_instances函数
nova/conductor/api.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): self.conductor_compute_rpcapi.build_instances(context, instances=instances, image=image, 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=legacy_bdm)
其中,self.conductor_compute_rpcapi= rpcapi.ComputeTaskAPI() !
3、接着看self.conductor_compute_rpcapi的build_instances函数
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): instances_p = [jsonutils.to_primitive(inst) for inst in instances] image_p = jsonutils.to_primitive(image) cctxt = self.client.prepare(version=\'1.5\') cctxt.cast(context, \'build_instances\', instances=instances_p, image=image_p, 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=legacy_bdm)
其中self.client = self.get_client(namespace=self.RPC_API_NAMESPACE),即 cctxt = self.client.prepare(version=\'1.5\')调用后cctxt为nova.rpcclient.RPCClient,根据http://www.cnblogs.com/littlebugfish/p/4058007.html中简述的消息队列调用原理,这里将转到nova-conductor进程中进行处理。
二、在nova-conductor进程中进行处理
nova/conductor/manager.py
ComputeTaskManager类: def build_instances(self, context, instances, image, filter_properties, admin_password, injected_files, requested_networks, security_groups, block_device_mapping, legacy_bdm=True): ... self.scheduler_rpcapi.run_instance(context, request_spec=request_spec, admin_password=admin_password, injected_files=injected_files, requested_networks=requested_networks, is_first_time=True, filter_properties=filter_properties, legacy_bdm_in_spec=legacy_bdm)
其中,self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI(),而scheduler_rpcapi为nova.scheduler.rpcapi,则self.scheduler_rpcapi为nova.scheduler.rpcapi.SchedulerAPI类的对象,接着看代码如下:
nova/scheduler/rpcapi.py
SchedulerAPI类: def run_instance(self, ctxt, request_spec, admin_password, injected_files, requested_networks, is_first_time, filter_properties, legacy_bdm_in_spec=True): version = \'2.0\' msg_kwargs = {\'request_spec\': request_spec, \'admin_password\': admin_password, \'injected_files\': injected_files, \'requested_networks\': requested_networks, \'is_first_time\': is_first_time, \'filter_properties\': filter_properties} if self.client.can_send_version(\'2.9\'): version = \'2.9\' msg_kwargs[\'legacy_bdm_in_spec\'] = legacy_bdm_in_spec cctxt = self.client.prepare(version=version) return cctxt.cast(ctxt, \'run_instance\', **msg_kwargs)
根据http://www.cnblogs.com/littlebugfish/p/4058007.html中简述的消息队列调用原理,这里将转到nova-scheduler进程中进行处理。
三、在nova-scheduler进程中进行处理
nova/scheduler/manager.py
SchedulerManager类: def run_instance(self, context, request_spec, admin_password, injected_files, requested_networks, is_first_time, filter_properties, legacy_bdm_in_spec=True): instance_uuids = request_spec[\'instance_uuids\'] with compute_utils.EventReporter(context, conductor_api.LocalAPI(), \'schedule\', *instance_uuids): try: return self.driver.schedule_run_instance(context, request_spec, admin_password, injected_files, requested_networks, is_first_time, filter_properties, legacy_bdm_in_spec) ...
这里需要看self.driver究竟为哪个类的对象。在SchedulerManager类的__init__函数中,有如下代码:
if not scheduler_driver: scheduler_driver = CONF.scheduler_driver self.driver = importutils.import_object(scheduler_driver)
根据配置,CONF.scheduler_driver为nova.scheduler.filter_scheduler.FilterScheduler,所以self.driver为nova.scheduler.filter_scheduler.FilterScheduler类的对象,所以接下来看:
nova.scheduler.filter_scheduler.py
FilterScheduler类: def schedule_run_instance(self, context, request_spec, admin_password, injected_files, requested_networks, is_first_time, filter_properties, legacy_bdm_in_spec): ... """选用来创建虚拟机的主机""" weighed_hosts = self._schedule(context, request_spec, filter_properties, instance_uuids) ... for num, instance_uuid in enumerate(instance_uuids): request_spec[\'instance_properties\'][\'launch_index\'] = num try: try: weighed_host = weighed_hosts.pop(0) LOG.info(_("Choosing host %(weighed_host)s " "for instance %(instance_uuid)s"), {\'weighed_host\': weighed_host, \'instance_uuid\': instance_uuid}) except IndexError: raise exception.NoValidHost(reason="") """创建虚拟机""" self._provision_resource(context, weighed_host, request_spec, filter_properties, requested_networks, injected_files, admin_password, is_first_time, instance_uuid=instance_uuid, legacy_bdm_in_spec=legacy_bdm_in_spec) except Exception as ex: driver.handle_schedule_error(context, ex, instance_uuid, request_spec) retry = filter_properties.get(\'retry\', {}) retry[\'hosts\'] = [] self.notifier.info(context, \'scheduler.run_instance.end\', payload)
1、 选择用来创建虚拟机的主机,代码如下:
weighed_hosts = self._schedule(context, request_spec,
filter_properties, instance_uuids)
接着看_schedule函数,如下:
def _schedule(self, context, request_spec, filter_properties, instance_uuids=None): ... hosts = self.host_manager.get_all_host_states(elevated) selected_hosts = [] if instance_uuids: num_instances = len(instance_uuids) else: num_instances = request_spec.get(\'num_instances\', 1) for num in xrange(num_instances): """根据规则过滤掉特定的主机。例如可以设定忽略某些主机,这样就不会选择这些主机来创建虚拟机""" hosts = self.host_manager.get_filtered_hosts(hosts, filter_properties, index=num) if not hosts: break LOG.debug(_("Filtered %(hosts)s"), {\'hosts\': hosts}) """根据规则为候选主机分配权重以便选取最佳的主机创建虚拟机,此处暂时不详述权重分配的过程""" weighed_hosts = self.host_manager.get_weighed_hosts(hosts, filter_properties) LOG.debug(_("Weighed %(hosts)s"), {\'hosts\': weighed_hosts}) scheduler_host_subset_size = CONF.scheduler_host_subset_size if scheduler_host_subset_size > len(weighed_hosts): scheduler_host_subset_size = len(weighed_hosts) if scheduler_host_subset_size < 1: scheduler_host_subset_size = 1 chosen_host = random.choice( weighed_hosts[0:scheduler_host_subset_size]) selected_hosts.append(chosen_host) chosen_host.obj.consume_from_instance(instance_properties) if update_group_hosts is True: filter_properties[\'group_hosts\'].append(chosen_host.obj.host) return selected_hosts
2、开始创建虚拟机,代码如下:
self._provision_resource(context, weighed_host, request_spec, filter_properties, requested_networks, injected_files, admin_password, is_first_time, instance_uuid=instance_uuid, legacy_bdm_in_spec=legacy_bdm_in_spec)
接着看_provision_resource函数,如下:
def _provision_resource(self, context, weighed_host, request_spec, filter_properties, requested_networks, injected_files, admin_password, is_first_time, instance_uuid=None, legacy_bdm_in_spec=True): ... try: """更新数据库""" updated_instance = driver.instance_update_db(context, instance_uuid, extra_values=values) except exception.InstanceNotFound: LOG.warning(_("Instance disappeared during scheduling"), context=context, instance_uuid=instance_uuid) else: scheduler_utils.populate_filter_properties(filter_properties, weighed_host.obj) """这里self.compute_rpcapi为 nova.compute.rpcapi.ComputeAPI对象""" self.compute_rpcapi.run_instance(context, instance=updated_instance, host=weighed_host.obj.host, request_spec=request_spec, filter_properties=filter_properties, requested_networks=requested_networks, injected_files=injected_files, admin_password=admin_password, is_first_time=is_first_time, node=weighed_host.obj.nodename, legacy_bdm_in_spec=legacy_bdm_in_spec)
nova/compute/rpcapi.py
ComputeAPI类: def run_instance(self, ctxt, instance, host, request_spec, filter_properties, requested_networks, injected_files, admin_password, is_first_time, node=None, legacy_bdm_in_spec=True): instance_p = jsonutils.to_primitive(instance) msg_kwargs = {\'instance\': instance_p, \'request_spec\': request_spec, \'filter_properties\': filter_properties, \'requested_networks\': requested_networks, \'injected_files\': injected_files, \'admin_password\': admin_password, \'is_first_time\': is_first_time, \'node\': node} if _icehouse_compat() or self.client.can_send_version(\'2.37\'): version = _get_version(\'2.37\') msg_kwargs[\'legacy_bdm_in_spec\'] = legacy_bdm_in_spec else: version = \'2.19\' cctxt = self.client.prepare(server=host, version=version) cctxt.cast(ctxt, \'run_instance\', **msg_kwargs)
根据http://www.cnblogs.com/littlebugfish/p/4058007.html中简述的消息队列调用原理,这里将转到nova-compute进程中进行处理。
四、在nova-compute进程中进行处理
nova/compute/manager.py
ComputeManager类: @wrap_exception() @reverts_task_state @wrap_instance_event @wrap_instance_fault def run_instance(self, context, instance, request_spec=None, filter_properties=None, requested_networks=None, injected_files=None, admin_password=None, is_first_time=False, node=None, legacy_bdm_in_spec=True): if filter_properties is None: filter_properties = {} @utils.synchronized(instance[\'uuid\']) def do_run_instance(): self._run_instance(context, request_spec, filter_properties, requested_networks, injected_files, admin_password, is_first_time, node, instance, legacy_bdm_in_spec) do_run_instance()
def _run_instance(self, context, request_spec, filter_properties, requested_networks, injected_files, admin_password, is_first_time, node, instance, legacy_bdm_in_spec): ... instance, network_info = self._build_instance(context, request_spec, filter_properties, requested_networks, injected_files, admin_password, is_first_time, node, instance, image_meta, legacy_bdm_in_spec) notify("end", msg=_("Success"), network_info=network_info) ...
def _build_instance(self, context, request_spec, filter_properties, requested_networks, injected_files, admin_password, is_first_time, node, instance, image_meta, legacy_bdm_in_spec): context = context.elevated() ... try: limits = filter_properties.get(\'limits\', {}) with rt.instance_claim(context, instance, limits): ... instance = self._spawn(context, instance, image_meta, network_info, block_device_info, injected_files, admin_password, set_access_ip=set_access_ip) ... # spawn success return instance, network_info
def _spawn(self, context, instance, image_meta, network_info, block_device_info, injected_files, admin_password, set_access_ip=False): """Spawn an instance with error logging and update its power state.""" instance = self._instance_update(context, instance[\'uuid\'], vm_state=vm_states.BUILDING, task_state=task_states.SPAWNING, expected_task_state=task_states.BLOCK_DEVICE_MAPPING) try: self.driver.spawn(context, instance, image_meta, injected_files, admin_password, network_info, block_device_info) ... return self._instance_update(context, instance[\'uuid\'], **update_data)
这里将调用相应的Driver对象进行虚拟机的创建,此处以LibvirtDriver类对象为例,后续处理过程可以查看:http://www.cnblogs.com/littlebugfish/p/4058115.html中的分析!