在经过nova-scheduler的过滤和排序后,请求会发送到相应host的nova-compute中,调用nova.compute.manager.ComputeManager.run_instance():
@wrap_exception()
@reverts_task_state
@wrap_instance_event
@wrap_instance_fault
def run_instance(self, context, instance, request_spec,
filter_properties, requested_networks,
injected_files, admin_password,
is_first_time, node, legacy_bdm_in_spec):
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()
最后会调用_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):
"""Launch a new instance with specified options."""
extra_usage_info = {}
def notify(status, msg="", fault=None, **kwargs):
"""Send a create.{start,error,end} notification."""
type_ = "create.%(status)s" % dict(status=status)
info = extra_usage_info.copy()
info['message'] = unicode(msg)
self._notify_about_instance_usage(context, instance, type_,
extra_usage_info=info, fault=fault, **kwargs)
try:
self._prebuild_instance(context, instance)
if request_spec and request_spec.get('image'):
image_meta = request_spec['image']
else:
image_meta = {}
extra_usage_info = {"image_name": image_meta.get('name', '')}
notify("start") # notify that build is starting
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)
self._build_instance()最终执行创建虚拟机的操作:
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):
...
try:
...
with rt.instance_claim(context, instance, limits):
...
network_info = self._allocate_network(context, instance,
requested_networks, macs, security_groups,
dhcp_options)
...
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
着重看self._allocate_network()和self._spawn()这两个函数。self._allocate_network()主要任务是调用network-api(我们使用的是neutron)在选择的network上创建相应的网络设备。self._spawn()是调用相应的driver去创建虚拟机。我们在这里只关注_spawn():
@object_compat
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.vm_state = vm_states.BUILDING
instance.task_state = task_states.SPAWNING
instance.save(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)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception(_('Instance failed to spawn'), instance=instance)
current_power_state = self._get_power_state(context, instance)
instance.power_state = current_power_state
instance.vm_state = vm_states.ACTIVE
instance.task_state = None
instance.launched_at = timeutils.utcnow()
函数前半部分和后半部分主要是更新一些虚拟机的状态,self.driver.spawn()会调用相应的driver,我们这里使用的是nova.virt.libvirt.LibvirtDriver,看它的spawn():
def spawn(self, context, instance, image_meta, injected_files,
admin_password, network_info=None, block_device_info=None):
...
self._create_image(context, instance,
disk_info['mapping'],
network_info=network_info,
block_device_info=block_device_info,
files=injected_files,
admin_pass=admin_password)
xml = self.to_xml(context, instance, network_info,
disk_info, image_meta,
block_device_info=block_device_info,
write_to_disk=True)
self._create_domain_and_network(context, xml, instance, network_info,
block_device_info)
...
self._create_image()是获取虚拟机的镜像,self.to_xml()是根据instance信息生成了一个名为libvirt.xml的libvirt domain配置,然后调用self._create_domain_and_network():
def _create_domain_and_network(self, context, xml, instance, network_info,
block_device_info=None, power_on=True,
reboot=False, vifs_already_plugged=False):
...
try:
with self.virtapi.wait_for_instance_event(
instance, events, deadline=timeout,
error_callback=self._neutron_failed_callback):
self.plug_vifs(instance, network_info)
self.firewall_driver.setup_basic_filtering(instance,
network_info)
self.firewall_driver.prepare_instance_filter(instance,
network_info)
domain = self._create_domain(
xml, instance=instance,
launch_flags=launch_flags,
power_on=power_on)
self.firewall_driver.apply_instance_filter(instance,
network_info)
...
return domain
因为之前_allocate_network()已经为虚拟机创建了相应的port,self.plug_vifs()就负责将这些port和虚拟机的网络接口连接起来。假设network使用的是neutron,driver使用的是openvswitch,那么这个操作其实就是使用ovs-vsctl add-port为虚拟机添加ovs port。如果在这里还为虚拟机添加了security group的话,那么还需要在这个port上添加linux bridge和veth pair,将虚拟机的tap和ovs port连接起来(因为ovs port无法进行iptables过滤):
port创建好之后,通过self.firewall_driver根据安全组为虚拟机创建访问规则。 self._create_domain()是我们这篇文章的主角,它通过libvirt driver的connection调用hypervisor去执行相关的虚拟机创建和启动等操作:
def _create_domain(self, xml=None, domain=None,
instance=None, launch_flags=0, power_on=True):
...
if xml:
try:
domain = self._conn.defineXML(xml)
except Exception as e:
LOG.error(_("An error occurred while trying to define a domain"
" with xml: %s") % xml)
raise e
if power_on:
try:
domain.createWithFlags(launch_flags)
except Exception as e:
with excutils.save_and_reraise_exception():
LOG.error(_("An error occurred while trying to launch a "
"defined domain with xml: %s") %
domain.XMLDesc(0))
...
return domain
self._conn.defineXML()是根据xml定义一个 domain,其实就相当于:
virsh define libvirt.xml
最后,如果启动的话,就使用domain.createWithFlags()去启动一个虚拟机,在这里它是调用的libvirt python binding interface去和libvirt通信。关于libvirt python binding,可以看这里。
这样的话,一个虚拟机的核心创建流程就结束了。