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中的分析!