Nova创建虚拟机的底层代码分析
作为个人学习笔记分享。有不论什么问题欢迎交流!
在openstack中创建虚拟机的底层实现是nova使用了libvirt,代码在nova/virt/libvirt/driver.py。
#image_meta:镜像的相关内容,#injected_files:要注入到VM的文件 #network_info:网络相关信息。block_device_info:磁盘相关信息 def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info=None, block_device_info=None): #确定客户机的磁盘映射关系 disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type, instance, block_device_info, image_meta) #创建VM的磁盘文件 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) #综合各方面的信息,拼装一个define VM的XML文件 xml = self.to_xml(context, instance, network_info, disk_info, image_meta, block_device_info=block_device_info, write_to_disk=True) #向neutron请求IP,然后使用xml创建domain self._create_domain_and_network(context, xml, instance, network_info, block_device_info)
以下将具体分析上述4个函数,比源码略有删减
#virt_type: cpu_mode,一般为kvm,具体可參见该选项的凝视 def get_disk_info(virt_type, instance, block_device_info=None, image_meta=None, rescue=False): #依据device_type和virt_type返回总线类型。如kvm和disk。则会返回virtio #cdrom和kvm,则会返回ide disk_bus = get_disk_bus_for_device_type(virt_type, image_meta, "disk") cdrom_bus = get_disk_bus_for_device_type(virt_type, image_meta, "cdrom") #确定如何映射默认的disks到VM中,如root挂到/dev/vda等。还有swap, local等 mapping = get_disk_mapping(virt_type, instance, disk_bus, cdrom_bus, block_device_info, image_meta, rescue)
#准备磁盘文件 def _create_image(self, context, instance, disk_mapping, suffix=\'\', disk_images=None, network_info=None, block_device_info=None, files=None, admin_pass=None, inject_files=True): #推断是否从volume启动VM booted_from_volume = self._is_booted_from_volume( instance, disk_mapping) #依据images_type。如qcow2, RBD等,创建各种格式的镜像相应的类 def image(fname, image_type=CONF.libvirt.images_type): return self.image_backend.image(instance, fname + suffix, image_type) #创建raw格式的镜像相应的类 def raw(fname): return image(fname, image_type=\'raw\') LOG.info(_(\'Creating image\'), instance=instance) if not disk_images: disk_images = {\'image_id\': instance[\'image_ref\'], \'kernel_id\': instance[\'kernel_id\'], \'ramdisk_id\': instance[\'ramdisk_id\']} if disk_images[\'kernel_id\']: fname = imagecache.get_cache_fname(disk_images, \'kernel_id\') #这里用到了上面定义的def raw()。其返回一个raw类型的类。cache()使用#fetch_image()完毕镜像文件的kernel下载,关于镜像的下载具体情况能够參#考:http://blog.csdn.net/epugv/article/details/27970625 raw(\'kernel\').cache(fetch_func=libvirt_utils.fetch_image, context=context, filename=fname, image_id=disk_images[\'kernel_id\'], user_id=instance[\'user_id\'], project_id=instance[\'project_id\']) if disk_images[\'ramdisk_id\']: fname = imagecache.get_cache_fname(disk_images, \'ramdisk_id\') #完毕ramdisk文件下载 raw(\'ramdisk\').cache(fetch_func=libvirt_utils.fetch_image, context=context, filename=fname, image_id=disk_images[\'ramdisk_id\'], user_id=instance[\'user_id\'], project_id=instance[\'project_id\']) # 创建暂时磁盘,ephemeral disk指的是除了root disk和swap disk之外的ephemeral #空间 ephemeral_gb = instance[\'ephemeral_gb\'] if \'disk.local\' in disk_mapping: disk_image = image(\'disk.local\') fn = functools.partial(self._create_ephemeral, fs_label=\'ephemeral0\', os_type=instance["os_type"], is_block_dev=disk_image.is_block_dev) fname = "ephemeral_%s_%s" % (ephemeral_gb, os_type_with_default) size = ephemeral_gb * units.Gi disk_image.cache(fetch_func=fn, filename=fname, size=size, ephemeral_size=ephemeral_gb) #假设还有ephemeral disk,继续创建。label为\'ephemeral%d\' % idx for idx, eph in enumerate(driver.block_device_info_get_ephemerals( block_device_info)): disk_image = image(blockinfo.get_eph_disk(idx)) fn = functools.partial(self._create_ephemeral, fs_label=\'ephemeral%d\' % idx, os_type=instance["os_type"], is_block_dev=disk_image.is_block_dev) size = eph[\'size\'] * units.Gi fname = "ephemeral_%s_%s" % (eph[\'size\'], os_type_with_default) disk_image.cache( fetch_func=fn, filename=fname, size=size, ephemeral_size=eph[\'size\']) #创建swap disk if swap_mb > 0: size = swap_mb * units.Mi image(\'disk.swap\').cache(fetch_func=self._create_swap, filename="swap_%s" % swap_mb, size=size, swap_mb=swap_mb) # OpenStack还能够使用Config Drive实现元数据的注入,可是在制作image时一 #定要安装cloud-init软件,否则无法实现元数据注入 # Config drive if configdrive.required_by(instance): LOG.info(_(\'Using config drive\'), instance=instance) extra_md = {} if admin_pass: extra_md[\'admin_pass\'] = admin_pass #提取instance matedata inst_md = instance_metadata.InstanceMetadata(instance, content=files, extra_md=extra_md, network_info=network_info) with configdrive.ConfigDriveBuilder(instance_md=inst_md) as cdb: configdrive_path = self._get_disk_config_path(instance) LOG.info(_(\'Creating config drive at %(path)s\'), {\'path\': configdrive_path}, instance=instance) #将metadata写入实例所在的目录下的一个disk.config文件里 try: cdb.make_drive(configdrive_path) except processutils.ProcessExecutionError as e: with excutils.save_and_reraise_exception(): LOG.error(_(\'Creating config drive failed \' \'with error: %s\'), e, instance=instance) #依据官方文档 -2 => disable, -1 => inspect (libguestfs only), 0 => #not partitioned, >0 => partition number # File injection only if needed elif inject_files and CONF.libvirt.inject_partition != -2: if booted_from_volume: LOG.warn(_(\'File injection into a boot from volume \' \'instance is not supported\'), instance=instance) self._inject_data( instance, network_info, admin_pass, files, suffix)
def to_xml(self, context, instance, network_info, disk_info, image_meta=None, rescue=None, block_device_info=None, write_to_disk=False): # We should get image metadata every time for generating xml if image_meta is None: image_ref = instance[\'image_ref\'] image_meta = compute_utils.get_image_metadata( context, self._image_api, image_ref, instance) msg = (\'Start to_xml \' \'network_info=%(network_info)s \' \'disk_info=%(disk_info)s \' \'image_meta=%(image_meta)s rescue=%(rescue)s \' \'block_device_info=%(block_device_info)s\' % {\'network_info\': network_info_str, \'disk_info\': disk_info, \'image_meta\': image_meta, \'rescue\': rescue, \'block_device_info\': block_device_info}) #返回guest的具体參数 conf = self.get_guest_config(instance, network_info, image_meta, disk_info, rescue, block_device_info) xml = conf.to_xml()#将具体參数使用etree.tostring()生产一个xml dom
def _create_domain_and_network(self, context, xml, instance, network_info, block_device_info=None, power_on=True, reboot=False, vifs_already_plugged=False): for vol in block_device_mapping: connection_info = vol[\'connection_info\'] disk_info = blockinfo.get_info_from_bdm( CONF.libvirt.virt_type, vol) #挂载volume前与volume建立连接,如iscsi中的discover conf = self.volume_driver_method(\'connect_volume\', connection_info, disk_info) #。。。。。。 launch_flags = events and libvirt.VIR_DOMAIN_START_PAUSED or 0 try: with self.virtapi.wait_for_instance_event( instance, events, deadline=timeout, error_callback=self._neutron_failed_callback): self.plug_vifs(instance, network_info)#向network注入VIF self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) domain = self._create_domain(#调用libvirt的#defineXML () and #createWithFlags() xml, instance=instance, launch_flags=launch_flags, power_on=power_on) #应用防火墙 self.firewall_driver.apply_instance_filter(instance, network_info) #以下是错误处理。不表