OpenStack之Nova分析——建立虛擬機器(七)——建立虛擬機器映象檔案
虛擬機器的映象檔案主要是指磁碟映象檔案,當然有的還包括ramdisk和kernel映象來配合磁碟映象檔案使用,這篇文章我們來重點分析一下建立虛擬機器磁碟映象的整個過程。
虛擬機器磁碟映象是虛擬機器正常執行不可缺少的映象檔案,它是虛擬機器的主磁碟。先來概述一下建立虛擬機器磁碟映象的整個流程:
1. LibvirtDriver類中的_create_img方法,完成虛擬機器映象檔案的建立工作的準備工作後,呼叫Qcow2類的cache方法。
2. Qcow2類的cache方法呼叫create_image方法,首先檢查本地計算節點上是否快取了虛擬機器所需的磁碟映象檔案。如果不存在快取檔案,則呼叫libvirt_utils包的fetch_image方法從Glance伺服器下載。然後,Qcow2類的create_image方法呼叫copy_qcow2_img方法建立qcow2格式的虛擬機器磁碟映象檔案。
3. libvirt_utils包的fetch_image方法呼叫fetch_to_raw方法。fetch_to_raw方法首先呼叫fetch方法從Glance伺服器上下載映象檔案,然後檢查下載的映象檔案格式是否正確,如果下載的不是raw格式,則將其轉化為raw格式。
4. Qcow2類的copy_qcow2_img方法通過執行qemu-img命令完成虛擬機器磁碟映象檔案的建立,建立的虛擬機器磁碟映象檔案會把換成的映象檔案作為baking file。
接著上一篇文章,來分析建立虛擬機器映象檔案的程式碼
class LibvirtDriver(driver.ComputeDriver): def _create_image(self, context, instance, disk_mapping, suffix='', disk_images=None, network_info=None, block_device_info=None, files=None, admin_pass=None): #預設的磁碟映象檔案字尾為空 if not suffix: suffix = '' ... #建立儲存虛擬機器磁碟映象的目錄 fileutils.ensure_tree(basepath(suffix='') ... 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']) ...
為了便於理解,我們先看一下傳入的“instance”變數中儲存的是什麼?
逐一分析一下_create_image方法:{'vm_state': 'building', 'availability_zone': None, 'terminated_at': None, 'ephemeral_gb': 0, 'instance_type_id': 4, 'user_data': 'IyEvYmluL2Jhc2gKdXNlcmFkZCAtbSByb290CnBhc3N3ZCByb290IDw8IEVPRgpyb29vdApyb29vdApFT0YKcGFzc3dkIHJvb3QgPDwgRU9GCnJvb290CnJvb290CkVPRgo=', 'vm_mode': None, 'deleted_at': None, 'reservation_id': 'r-ze5a808m', 'id': 70, 'security_groups': [{'project_id': 'e2c96ea3efe0418cb86fef29aabc2725', 'user_id': 'cc4f87ee9b0d43a4bfe6d1113473f796', 'name': 'default', 'deleted': False, 'created_at': '2014-11-10T02:43:37.000000', 'updated_at': None, 'rules': [...], 'disable_terminate': False, 'root_device_name': None, 'user_id': 'cc4f87ee9b0d43a4bfe6d1113473f796', 'uuid': '19df2538-47c2-49f1-8735-afbc24e68743', 'server_name': None, 'default_swap_device': None, 'info_cache': {'instance_uuid': '19df2538-47c2-49f1-8735-afbc24e68743', 'deleted': False, 'created_at': '2015-02-12T06:05:27.000000', 'updated_at': None, 'network_info': '[]', 'deleted_at': None, 'id': 70}, 'hostname': 'test', 'launched_on': None, 'display_description': 'test', 'key_data': None, 'deleted': False, 'scheduled_at': '2015-02-12T06:05:28.000000', 'power_state': 0, 'default_ephemeral_device': None, 'progress': 0, 'project_id': 'e2c96ea3efe0418cb86fef29aabc2725', 'launched_at': None, 'config_drive': '', 'node': 'sts-zestack-02', 'ramdisk_id': '', 'access_ip_v6': None, 'access_ip_v4': None, 'kernel_id': '', 'key_name': None, 'updated_at': '2015-02-12T06:05:28.446678', 'host': 'sts-zestack-02', 'display_name': 'test', 'system_metadata': [...], 'task_state': 'scheduling', 'shutdown_terminate': False, 'cdrom_active': False, 'root_gb': 0, 'locked': False, 'name': 'instance-00000046', 'hypervisor': 'libvirt', 'created_at': '2015-02-12T06:05:27.000000', 'launch_index': 0, 'memory_mb': 512, 'instance_type': {'memory_mb': 512, 'root_gb': 0, 'name': 'm1.tiny', 'deleted': False, 'created_at': None, 'ephemeral_gb': 0, 'updated_at': None, 'disabled': False, 'vcpus': 1, 'flavorid': '1', 'swap': 0, 'rxtx_factor': 1.0, 'is_public': True, 'deleted_at': None, 'vcpu_weight': None, 'id': 4}, 'vcpus': 1, 'image_ref': '11d25bff-1091-4f7a-9a3c-0e48be0cc21b', 'architecture': None, 'auto_disk_config': None, 'os_type': None, 'metadata': []}
1. ensure_tree方法
fileutils物件的ensure方法會檢查儲存虛擬機器映象檔案的目錄是否存在,其中的basepath方法是內部方法
#獲取儲存虛擬機器映象檔案的目錄
def basepath(fname='', suffix=suffix):
return os.path.join(libvirt_utils.get_instance_path(instance),
fname + suffix)
這個方法最終返回的是儲存虛擬機器映象檔案的目錄。
2. get_cache_fname方法
imagecache物件的get_cache_fname方法的作用是獲取快取在本地的磁碟映象檔名。在Nova的設計過程中,為了減輕傳輸磁碟映象檔案對網路造成的負擔,可以將計算節點中用到的磁碟映象檔案儲存在本地。使用者可以在nova.conf配置檔案中通過cache_images配置項進行配置,cache_images=all表示快取所有的映象檔案,cache_images=some表示快取cache_in_nova=True的映象檔案,cache_images=none表示不快取映象檔案。
3. cache方法
分析這個方法前,得先看一下image方法
def image(fname, image_type=CONF.libvirt_images_type):
return self.image_backend.image(instance,
fname + suffix, image_type)
其中image_backend是Backend類的物件,所以該方法的返回值是Backend類下image方法的返回值
class Backend(object):
def image(self, instance, disk_name, image_type=None):
backend = self.backend(image_type)
return backend(instance=instance, disk_name=disk_name)
可見該方法會根據實際的需要,建立並返回一個相應型別的backend物件,這些型別包括raw、qcow2和lvm格式的映象。這裡我配置的是qcow2格式的映象,所以backend物件的image方法會建立並返回一個Qcow2類。
現在可以來看這個cache方法了,通過上面的分析,我們知道,這個cache方法實際上是Qcow2類的cache方法,Qcow2類繼承自Image類,cache方法定義下Image類中,這個方法定義如下
class Image(object):
def cache(self, fetch_func, filename, size=None, *args, **kwargs):
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目錄
base_dir = os.path.join(CONF.instances_path, CONF.base_dir_name)
#建立base目錄
if not os.path.exists(base_dir):
fileutils.ensure_tree(base_dir)
#獲取快取的映象檔名
base = os.path.join(base_dir, filename)
#如果虛擬機器映象檔案或快取的映象檔案不存在,則建立
if not os.path.exists(self.path) or not os.path.exists(base):
self.create_image(call_if_not_exists, base, size,
*args, **kwargs)
...
這個方法首先獲取快取映象檔案的_base目錄,這裡的目錄是通過nova.conf配置檔案進行配置的。通常的配置為instances_path=/opt/stack/data/instance,base_dir_name=_base,所以拼接後的映象檔案的快取目錄為/opt/stack/data/_base。接下來,如果上述目錄不存在,則會建立這個目錄。完成針對目錄的操作後,會檢查是否存在虛擬機器的映象檔案或快取的映象檔案,如果不存在,則會呼叫create_image方法來建立一個映象檔案。
我們來分析create_image這個方法。Qcow2類過載了其父類Image類的create_image方法,其定義如下
class Qcow2(Image):
def create_image(self, prepare_template, base, size, *args, **kwargs):
#如果base目錄下不存在快取的映象檔案,則從Glance伺服器下載
if not os.path.exists(base):
prepare_template(target=base, *args, **kwargs)
...
#如果映象檔案沒有建立,則建立映象檔案
if not os.path.exists(self.path):
with utils.remove_path_on_error(self.path):
copy_qcow2_image(base, self.path, size)
這個方法分別呼叫了prepare_template方法和copy_qcow2_image方法。如果在base目錄下沒有發現快取的映象檔案,則呼叫prepare_template方法,從Glance伺服器下載映象檔案並快取到base目錄下;如果發現映象檔案沒有建立,則呼叫copy_qcow2_image方法建立映象檔案。
下面分別來分析這兩個方法:
1. prepare_template方法
這個方法是Image類的cache方法以引數的形式傳遞過來的,其傳入的引數為call_if_not_exists方法。從上面的程式碼中,可以看到call_if_not_exists方法的定義
def cache(self, fetch_func, filename, size=None, *args, **kwargs):
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)
...
該方法是cache方法的內部方法,還是會再次檢查快取的映象檔案是否存在,如果不存在,則會呼叫fetch_func方法從Glance伺服器上下載。fetch_func方法同樣是通過引數傳遞進來的,檢視上面LibertDriver類的_create_image方法,我們看到,fetch_func的值為libvirt_utils包下fetch_image方法。其定義如下
def fetch_image(context, target, image_id, user_id, project_id):
images.fetch_to_raw(context, image_id, target, user_id, project_id)
fetch_image方法呼叫了images包下的fetch_to_raw方法,其定義如下
def fetch_to_raw(context, image_href, path, user_id, project_id):
#構造快取檔案的臨時檔名
path_tmp = "%s.part" % path
#將映象檔案從Glance伺服器下載並快取到本地
fetch(context, image_href, path_tmp, user_id, project_id)
with utils.remove_path_on_error(path_tmp):
#查詢快取的映象檔案的資訊
data = qemu_img_info(path_tmp)
#獲取快取映象檔案的格式
fmt = data.file_format
if fmt is None:
raise exception.ImageUnacceptable(
reason=_("'qemu-img info' parsing failed."),
image_id=image_href)
backing_file = data.backing_file
#Glance伺服器上的映象檔案不允許有backing_file
if backing_file is not None:
raise exception.ImageUnacceptable(image_id=image_href,
reason=_("fmt=%(fmt)s backed by: %(backing_file)s") % locals())
#將映象檔案轉化為raw格式
if fmt != "raw" and CONF.force_raw_images:
staged = "%s.converted" % path
LOG.debug("%s was %s, converting to raw" % (image_href, fmt))
with utils.remove_path_on_error(staged):
#將快取檔案轉化為raw格式
convert_image(path_tmp, staged, 'raw')
#刪除轉化前的格式
os.unlink(path_tmp)
#驗證轉化是否成功
data = qemu_img_info(staged)
if data.file_format != "raw":
raise exception.ImageUnacceptable(image_id=image_href,
reason=_("Converted to raw, but format is now %s") %
data.file_format)
os.rename(staged, path)
else:
os.rename(path_tmp, path)
fetch_to_raw方法主要做了3件事:
(1) 從Glance伺服器下載映象到本地
主要是呼叫了fetch方法從Glance伺服器下載映象檔案,fetch方法定義如下
def fetch(context, image_href, path, _user_id, _project_id):
(image_service, image_id) = glance.get_remote_image_service(context,
image_href)
with utils.remove_path_on_error(path):
with open(path, "wb") as image_file:
image_service.download(context, image_id, image_file)
glance物件的get_remote_image_service方法首先會構造一個用於連線Glance伺服器的客戶端,這個客戶端會向封裝並向Glance伺服器傳送HTTP請求,Glance伺服器會通過WSGI服務接收並處理這些請求。然後把剛剛構造的客戶端當作引數傳給GlanceImageService類並最終返回這個類的物件,即變數image_service,image_service物件下的download方法下載映象檔案。
(2) 檢查下載的映象檔案格式是否正確
首先,呼叫qemu_img_info方法查詢映象檔案的詳細資訊,這個方法相當於執行了qemu-img info命令。然後,獲取通過file_format方法獲取映象檔案格式,並判斷格式是否正確。最後,由於Glance伺服器上的映象檔案必須是獨立的,不允許有backing_file,所以還需要驗證一下是否存在backing_file。
(3) 將非raw格式的映象檔案轉化為raw格式
2. copy_qcow2_image方法
copy_qcow2_image方法是Qcow2類create_image方法的內部方法,其定義如下
def copy_qcow2_image(base, target, size):
#為虛擬機器建立qcow2格式的磁碟映象檔案
libvirt_utils.create_cow_image(base, target)
if size:
#擴充套件磁碟映象檔案的容量
disk.extend(target, size)
copy_qcow2_image方法首先會呼叫libvirt_utils包的create_cow_image方法為虛擬機器建立磁碟映象檔案。相當於執行了如下命令:
qemu-img create -f qcow2 -o backing_file=<base> size=<size> <target>
然後,copy_qcow2_image方法呼叫了disk包的extend方法擴充套件虛擬機器磁碟映象檔案的容量,相當於執行了如下命令:
qemu-img resize <target> <size> e2fsck -fp <target> resize2fs <target>
至此虛擬機器磁碟映象檔案的建立過程就分析完了~~