nova-virt與libvirt


源碼版本:H版

  nova通過nova/virt/driver.py中的ComputeDriver對底層虛擬化技術進行抽象,不同的虛擬化技術在nova/virt下有不同的目錄,里面均有driver.py文件,通過繼承ComputeDriver類來實現自己的Driver類。nova可以通過對Driver類進行統一接口的調用實現底層虛擬技術的管理。下面具體談談nova對libvirt的使用:

一、libvirt基礎

參考:http://www.cnblogs.com/littlebugfish/p/4231996.html

 

二、nova對libvirt的使用架構

  說明:其中libvirt-python僅僅是對libvirt的Python綁定,並沒有改變功能,對API的改動也不大。

 

三、nova創建虛擬機時對libvirt的調度

nova/virt/libvirt/driver.py

LibvirtDriver類:
def spawn(self, context, instance, image_meta, injected_files,
              admin_password, network_info=None, block_device_info=None):
    """獲取disk配置信息"""
    disk_info = blockinfo.get_disk_info(CONF.libvirt_type,
                                        instance,
                                        block_device_info,
                                        image_meta)
    """處理鏡像,見下文第1節"""
    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文件"""
    xml = self.to_xml(context, instance, network_info,
                      disk_info, image_meta,
                      block_device_info=block_device_info,
                      write_to_disk=True)
    """調用libvirt創建並啟動虛擬機,見下文第2節"""
    self._create_domain_and_network(context, xml, instance, network_info,
                                    block_device_info)
    ...

1、處理鏡像

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):
    ...
    def image(fname, image_type=CONF.libvirt_images_type):
        return self.image_backend.image(instance,
                                        fname + suffix, image_type)
    ...
"""初始化鏡像文件引用""" if not disk_images: disk_images = {'image_id': instance['image_ref'], 'kernel_id': instance['kernel_id'], 'ramdisk_id': instance['ramdisk_id']}
"""從kernel創建磁盤鏡像,一般不采用""" if disk_images['kernel_id']: fname = imagecache.get_cache_fname(disk_images, 'kernel_id') ... inst_type = flavors.extract_flavor(instance) """從image創建啟動磁盤鏡像""" if not booted_from_volume: root_fname = imagecache.get_cache_fname(disk_images, 'image_id') size = instance['root_gb'] * 1024 * 1024 * 1024 if size == 0 or suffix == '.rescue': size = None image('disk').cache(fetch_func=libvirt_utils.fetch_image, context=context, filename=root_fname, size=size, image_id=disk_images['image_id'], user_id=instance['user_id'], project_id=instance['project_id']) """創建臨時磁盤鏡像和swap磁盤鏡像,過程基本同創建啟動磁盤鏡像類似""" ... """配置驅動""" if configdrive.required_by(instance): LOG.info(_('Using config drive'), instance=instance) extra_md = {} ... """文件注入""" 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) ... if CONF.libvirt_type == 'uml': libvirt_utils.chown(image('disk').path, 'root')

  這里重點分析從image創建啟動磁盤鏡像的過程 。接着分析代碼如下:

image('disk').cache(fetch_func=libvirt_utils.fetch_image,
                    context=context,
                    filename=root_fname,
                    size=size,
                    image_id=disk_images['image_id'],
                    user_id=instance['user_id'],
                    project_id=instance['project_id'])

  首先來看image函數,它本身在_create_image函數中進行定義,如下:

def image(fname, image_type=CONF.libvirt_images_type):
    return self.image_backend.image(instance,
                                    fname + suffix, image_type)

  主要的作用是根據配置文件在nova.virt.libvirt.imagebackend模塊中選擇適當對象進行構造並返回,通常為Qcow2對象。

  接着來看cache函數。Qcow2類沒有定義該函數,但是由於在nova.virt.libvirt.imagebackend模塊中Qcow2類繼承自Image類,所以自然調用Image類中定義的該函數。兩個類的關系如下圖所示:

  具體代碼如下:

nova/virt/libvirt/imagebackend.py 

Image類:
def
cache(self, fetch_func, filename, size=None, *args, **kwargs): """構造獲取base鏡像的函數""" @utils.synchronized(filename, external=True, lock_path=self.lock_path) def call_if_not_exists(target, *args, **kwargs): if not os.path.exists(target): fetch_func(target=target, *args, **kwargs) elif CONF.libvirt_images_type == "lvm" and \ 'ephemeral_size' in kwargs: fetch_func(target=target, *args, **kwargs)
base_dir
= os.path.join(CONF.instances_path, CONF.base_dir_name) if not os.path.exists(base_dir): fileutils.ensure_tree(base_dir) base = os.path.join(base_dir, filename)   """如果不存在鏡像或不存在base鏡像的話""" if not self.check_image_exists() or not os.path.exists(base): self.create_image(call_if_not_exists, base, size, *args, **kwargs)
   ...

  由於創建虛擬機時建立的啟動磁盤鏡像為qcow2格式,所以需要base鏡像,鏡像格式可參考:http://docs.openstack.org/image-guide/content/ch_introduction.html。但此時還沒有啟動磁盤鏡像,所以需要調用Qcow2類的create_image函數進行創建,該函數代碼如下:

nova/virt/libvirt/imagebackend.py

Qcow2類: def create_image(self, prepare_template, base, size, *args, **kwargs):
    @utils.synchronized(base, external=True, lock_path=self.lock_path)
    def copy_qcow2_image(base, target, size):
        libvirt_utils.create_cow_image(base, target)
        if size:
            disk.extend(target, size, use_cow=True)

    """沒有base鏡像的話先下載base鏡像"""
    if not os.path.exists(base):
        prepare_template(target=base, max_size=size, *args, **kwargs) 
    else:
        self.verify_base_size(base, size)
    ...
"""有base鏡像后創建qcow2鏡像""" if not os.path.exists(self.path): with fileutils.remove_path_on_error(self.path): copy_qcow2_image(base, self.path, size)

  假設此時連base鏡像也沒有,則需要先下載base鏡像。從上面可以看出,prepare_template 函數即為cache 函數中傳入的call_if_not_exists 函數,call_if_not_exists 函數里面接着調用fetch_func 函數,而fetch_func在_create_image 函數中已經表明為fetch_func=libvirt_utils.fetch_image,所以繼續如下分析:

nova/virt/libvirt/utils.py

def fetch_image(context, target, image_id, user_id, project_id, max_size=0):
    """images為nova/virt/images.py"""
    images.fetch_to_raw(context, image_id, target, user_id, project_id,
                        max_size=max_size)

nova/virt/images.py

def fetch_to_raw(context, image_href, path, user_id, project_id, max_size=0):
    path_tmp = "%s.part" % path
    fetch(context, image_href, path_tmp, user_id, project_id,
          max_size=max_size)
    ...

def fetch(context, image_href, path, _user_id, _project_id, max_size=0):
    """其中glance為nova/image/glance.py模塊"""
    (image_service, image_id) = glance.get_remote_image_service(context,
                                                                image_href)
    with fileutils.remove_path_on_error(path):
        image_service.download(context, image_id, dst_path=path)

nova/image/glance.py

GlanceImageService類:
def download(self, context, image_id, data=None, dst_path=None):
    ...
    try:
        """使用glanceclient獲取鏡像數據"""
        image_chunks = self._client.call(context, 1, 'data', image_id)
    except Exception:
        _reraise_translated_image_exception(image_id)
    ...

  接着來看當有了base鏡像后,但此時仍然沒有啟動磁盤鏡像,所以需要接着創建啟動磁盤鏡像,回到create_image函數中進行分析,涉及的代碼如下:

nova/virt/libvirt/imagebackend.py

Qcow2類:
def create_image(self, prepare_template, base, size, *args, **kwargs):
    @utils.synchronized(base, external=True, lock_path=self.lock_path)
    def copy_qcow2_image(base, target, size):        
        libvirt_utils.create_cow_image(base, target)
        if size:
            disk.extend(target, size, use_cow=True)
    ...
    """有base鏡像后創建qcow2鏡像"""
    if not os.path.exists(self.path):
        with fileutils.remove_path_on_error(self.path):
            copy_qcow2_image(base, self.path, size)

  其中主要調用copy_qcow2_image函數,而copy_qcow2_image函數又調用create_cow_image函數,接着分析代碼:

nova/virt/libvirt/utils.py

def create_cow_image(backing_file, path, size=None):
    """構建命令創建qcow2鏡像,其中設置base鏡像為_base目錄下相應的鏡像,即backing_file"""
    base_cmd = ['qemu-img', 'create', '-f', 'qcow2']
    cow_opts = []
    if backing_file:
        cow_opts += ['backing_file=%s' % backing_file]
        base_details = images.qemu_img_info(backing_file)
    else:
        base_details = None
    if base_details and base_details.cluster_size is not None:
        cow_opts += ['cluster_size=%s' % base_details.cluster_size]
    if base_details and base_details.encryption:
        cow_opts += ['encryption=%s' % base_details.encryption]
    if size is not None:
        cow_opts += ['size=%s' % size]
    if cow_opts:
        csv_opts = ",".join(cow_opts)
        cow_opts = ['-o', csv_opts]
    cmd = base_cmd + cow_opts + [path]
    execute(*cmd) 

2、創建並啟動虛擬機

  繼續分析spawn函數中_create_domain_and_network函數的調用,具體代碼如下:

nova/virt/libvirt/driver.py

LibvirtDriver類:
def _create_domain_and_network(self, xml, instance, network_info,
                                   block_device_info=None, power_on=True,
                                   context=None, reboot=False):
    ...
    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, power_on=power_on)
    self.firewall_driver.apply_instance_filter(instance, network_info)
    return domain

    
def _create_domain(self, xml=None, domain=None,
                       instance=None, launch_flags=0, power_on=True):
    ...
    if xml:
        try:
            """self._conn為libvirt.virConnect,所以這里實際上是調用libvirt定義虛擬機"""
            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:
            """調用libvirt啟動虛擬機"""
            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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM