OpenStack基于Libvirt的虚拟化平台调度实现----Nova虚拟机启动源码实现(4)

时间:2021-01-22 08:43:12

感谢朋友支持本博客,欢迎共同探讨交流,由于能力和时间有限,错误之处在所难免,欢迎指正!
如果转载,请保留作者信息。
博客地址:http://blog.csdn.net/gaoxingnengjisuan
邮箱地址:dong.liu@siat.ac.cn


完成对方法_create_image的解析,我们回到方法spawn中,继续对Nova虚拟机启动源码实现进行解析。

再来看方法spawn的源码:

def spawn(self, context, instance, image_meta, injected_files,
admin_password, network_info=None, block_device_info=None):
"""
在虚拟化平台上建立新的实例/虚拟机/域;
# 调用之一传进来的参数:
# context:上下文信息;
# instance:实例信息;
# image_meta:从glance获取的镜像文件image的元数据;
# injected_files:编码后的注入文件;
# admin_password:admin密码;
# network_info=self._legacy_nw_info(network_info):转换为传统格式的网络资源信息;
# block_device_info:实例错误记录的块设备;
"""

# get_disk_info:确定来宾系统的磁盘映射信息;
# 返回disk_bus、cdrom_bus和mapping的值这些磁盘映射信息给disk_info;
# CONF.libvirt_type:这个参数定义了libvirt的域名类型,参数的默认值为'kvm';
# instance:实例信息;
# block_device_info:实例错误记录的块设备;
# image_meta:从glance获取的镜像文件image的元数据;

# get_disk_info返回值:
# return {'disk_bus': disk_bus, # 获取disk类型的磁盘总线;
# 'cdrom_bus': cdrom_bus, # 获取cdrom类型的磁盘总线;
# 'mapping': mapping} # 确定怎样映射从默认的磁盘到虚拟机,返回客户对于设备的磁盘映射;
disk_info = blockinfo.get_disk_info(CONF.libvirt_type,
instance,
block_device_info,
image_meta)

# to_xml:为新建立的实例参数获取配置数据conf,并把获取的数据conf转换为xml格式;
# instance:实例信息;
# network_info:转换为传统格式的网络资源信息;
# disk_info:来宾系统的磁盘映射信息;
# image_meta:从glance获取的镜像文件image的元数据;
# block_device_info:实例错误记录的块设备;
xml = self.to_xml(instance, network_info,
disk_info, image_meta,
block_device_info=block_device_info)

# _create_image:建立虚拟机实例镜像;
# context:上下文信息;
# instance:实例信息;
# xml:为新建立的实例参数获取配置数据conf,并把获取的数据conf转换为xml格式;
# disk_info['mapping']:来宾系统磁盘的映射信息;
# network_info=network_info:转换为传统格式的网络资源信息;
# block_device_info=block_device_info:实例错误记录的块设备;
# files=injected_files:编码后的注入文件;
# admin_pass=admin_password:admin密码;
self._create_image(context, instance, xml,
disk_info['mapping'],
network_info=network_info,
block_device_info=block_device_info,
files=injected_files,
admin_pass=admin_password)

# 执行所需网络的安装以及建立域;
self._create_domain_and_network(xml, instance, network_info,
block_device_info)
LOG.debug(_("Instance is running"), instance=instance)

def _wait_for_boot():
"""
在固定时间间隔调用,检测虚拟机启动状态,直到虚拟机成功运行;
"""
state = self.get_info(instance)['state']

if state == power_state.RUNNING:
LOG.info(_("Instance spawned successfully."),
instance=instance)
raise utils.LoopingCallDone()

# _wait_for_boot:在固定时间间隔调用,检测虚拟机启动状态,直到虚拟机成功运行;
# 以一定的时间间隔(0.5)循环调用_wait_for_boot方法;
timer = utils.FixedIntervalLoopingCall(_wait_for_boot)
timer.start(interval=0.5).wait()
在完成image镜像的建立之后,这里将会调用方法_create_domain_and_network来实现执行所需网络的安装和domain的建立以及虚拟机的启动。具体实现我们来看方法_create_domain_and_network的源代码:

def _create_domain_and_network(self, xml, instance, network_info,
block_device_info=None):

"""
执行所需网络的安装以及建立域;
"""
block_device_mapping = driver.block_device_info_get_mapping(block_device_info)

# 获取卷驱动方法,并进行卷的连接驱动;
for vol in block_device_mapping:
connection_info = vol['connection_info']
disk_dev = vol['mount_device'].rpartition("/")[2]

# 磁盘信息disk_info:
# 设备、磁盘总线、磁盘类型;
disk_info = {
'dev': disk_dev,
'bus': blockinfo.get_disk_bus_for_disk_dev(CONF.libvirt_type,disk_dev),
'type': 'disk',
}
# 匹配指定的方法connect_volume,实现链接卷,返回xml文件;
self.volume_driver_method('connect_volume',connection_info,disk_info)

# plug_vifs:为network_info中的每一个网络,增加VIF到网络中;
self.plug_vifs(instance, network_info)
# setup_basic_filtering:为network_info中的每一个网络设置基本的网络防火墙过滤器;
self.firewall_driver.setup_basic_filtering(instance, network_info)
# prepare_instance_filter:为实例准备过滤器,此时实例还没有运行;
self.firewall_driver.prepare_instance_filter(instance, network_info)
# _create_domain:建立一个domain,并运行实例;
domain = self._create_domain(xml, instance=instance)
self.firewall_driver.apply_instance_filter(instance, network_info)

return domain
这个方法主要完成了两个任务,一个是为实例的运行安装配置所需的网络信息,再有就是为实例建立一个domain,并运行实例。

其中方法_create_domain就是实现了建立domain并运行实例这个任务。具体的实现我们来看方法_create_domain的源代码:

def _create_domain(self, xml=None, domain=None,
instance=None, launch_flags=0):
"""
建立一个domain,并运行实例;
"""
inst_path = None
if instance:
# 确定实例存储的正确的路径;
inst_path = libvirt_utils.get_instance_path(instance)

if CONF.libvirt_type == 'lxc':
if not inst_path:
inst_path = None

container_dir = os.path.join(inst_path, 'rootfs')
# 按照path的路径信息,建立container_dir完整的路径,包括所有需要的上级路径;
fileutils.ensure_tree(container_dir)
# 为选择的后端构造镜像image;
image = self.image_backend.image(instance, 'disk')

disk.setup_container(image.path,
container_dir=container_dir,
use_cow=CONF.use_cow_images)

# 定义一个域domain,但是不启动它;
# 其中调用方法virDomainDefineXML,实现为一个持久性的域建立并存储配置文件;
if xml:
domain = self._conn.defineXML(xml)
# createWithFlags:启动一个已定义的域,如果调用成功,域将从定义的集合转移到运行域的集合;
# 返回启动虚拟机的结果;
domain.createWithFlags(launch_flags)
# XMLDesc:调用方法virDomainGetXMLDesc,来实现获取一个虚拟机(也就是一个Domain)的XML文件;
# 这个XML文件可能会被重复使用,调用方法virDomainCreateXML()用来重新启动这个域;
self._enable_hairpin(domain.XMLDesc(0))

if CONF.libvirt_type == 'lxc':
state = self.get_info(instance)['state']
container_dir = os.path.join(inst_path, 'rootfs')
if state == power_state.RUNNING:
disk.clean_lxc_namespace(container_dir=container_dir)
else:
disk.teardown_container(container_dir=container_dir)

return domain
我们逐条对这个方法的代码进行解析:

1.inst_path = libvirt_utils.get_instance_path(instance)

这条语句实现了确定实例存储的正确的路径;

2.if CONF.libvirt_type == 'lxc':

       ......

这部分语句判断CONF.libvirt_type的值是否是“lxc”,如果是的话,则执行其下面的语句。但是经阅读代码知道,系统默认的CONF.libvirt_type参数值为“kvm”,所以,这里我们暂且不对CONF.libvirt_type值为“lxc”的情况作解析。

3.domain = self._conn.defineXML(xml)

这条语句实现了根据前面配置生成的xml文件定义建立一个域domain,但是不启动它,其中调用了方法virDomainDefineXML,实现为一个持久性的域建立并存储配置文件。具体我们来看方法defineXML的源代码实现:

def defineXML(self, xml):
"""
根据配置好的xml文件定义一个域,但是不启动它;
"""
# virDomainDefineXML将会为一个持久性的域建立并存储配置文件;
ret = libvirtmod.virDomainDefineXML(self._o, xml)
if ret is None:raise libvirtError('virDomainDefineXML() failed', conn=self)
__tmp = virDomain(self,_obj=ret)
return __tmp
这个方法中调用了libvirt的python接口方法virDomainDefineXML,这个方法所实现的功能就是将会建立一个持久性的域并存储相关的配置文件。

4.domain.createWithFlags(launch_flags)

这条语句调用了方法createWithFlags实现了启动一个已定义的域domain,如果调用成功,域将从定义的集合转移到运行域的集合,并返回启动虚拟机的结果。具体我们来看方法createWithFlags的源代码实现:

def createWithFlags(self, flags):
"""
启动一个已定义的域,如果调用成功,域将从定义的集合转移到运行域的集合;
返回启动虚拟机的结果;
"""
# virDomainCreateWithFlags:这个libvirt API是实现虚拟机启动的;
ret = libvirtmod.virDomainCreateWithFlags(self._o, flags)
if ret == -1: raise libvirtError ('virDomainCreateWithFlags() failed', dom=self)
return ret
这个方法中调用了libvirt的python接口方法virDomainCreateWithFlags,这个方法所实现的功能就是实现虚拟机的启动的。

方法_create_domain的最后,返回已经建立执行的domain。

我们回到方法spawn,可以看到,在方法的最后,有这样的两条语句:

timer = utils.FixedIntervalLoopingCall(_wait_for_boot)
timer.start(interval=0.5).wait()
这两条语句所实现的功能是以一定的时间间隔(0.5)循环调用_wait_for_boot方法,来检测虚拟机启动状态,直到虚拟机成功运行。

至此,方法spawn全部解析完成,也就是说Nova通过libvirt库来实现虚拟机启动的实现源码解析完成。

博文中不免有不正确的地方,欢迎朋友们不吝批评指正,谢谢大家了!OpenStack基于Libvirt的虚拟化平台调度实现----Nova虚拟机启动源码实现(4)