openstack創建虛擬機源碼分析三(Glance下載鏡像)

創建虛擬機是個很好的應用實例,因爲幾乎包括了所有主要組件的應用。我們可以看到虛擬創建流程圖如下,很複雜,所以進行拆解,我們今天主要看標紅這部分(下載鏡像)

 

在理創建虛擬機時glance之前,我們先看看glance本身主要分幾塊,有個大概瞭解

glance 主要職責是:

  • 提供api讓用戶能夠查詢和獲取鏡像的元數據和鏡像本身
  • 支持多種方式存儲鏡像,包括普通的文件系統、Swift、Ceph 等;
  • 對實例執行快照創建新的鏡像。

查詢鏡像、上傳鏡像、下載鏡像這些都是glance的日常操作,我們會以獲取鏡像爲例,先簡單介紹一下glance組件

由圖2我們可以看到:

1.外部來的請求統一都由glance-api處理,繼續秉承openstack大家族的一貫風格。主要的職責又分爲兩部分

       1.1 做一些對請求的解析工作(如分析出版本號) 

       1.2 另外一部分提供實際的服務(如與鏡像上傳下載的後端存儲接口交互),實際服務glance-api的請求轉發原則是:

  •  如果是與鏡像 metadata(元數據)相關的操作,glance-api 會把請求轉發給 glance-registry;
  •  如果是與鏡像自身存取相關的操作,glance-api 會把請求轉發給該 image 的存儲後端。

 

2.glance-registry主要負責存儲或者獲取鏡像的元數據,與MySQL數據庫進行交互。例如鏡像的大小和類型。glance-registry也可以簡單的再細分爲兩部分,API和具體的Server。

這裏的元數據是指鏡像相關的一些信息(如id,size, status,location, checksum, min_disk, min_ram, owner等),有了這些數據,一是我們就可以判斷這個鏡像是不是我們想要的那個,二是這樣才能獲取到location去後端存儲去取這個鏡像。

 

3.後端存儲(支持很多種,用那種用戶自己選),用來真正存放鏡像本體的backend。因爲glance 自己並不存儲鏡像。

下面這些是glance 支持的許多種後端存儲的一些,比如:

  • A directory on a local file system:這是默認配置,在本地的文件系統裏進行保存鏡像。
  • GridFS:使用MongoDB存儲鏡像。
  • Ceph RBD:使用Ceph的RBD接口存儲到Ceph集羣中。
  • Amazon S3:亞馬遜的S3。
  • Sheepdog:專爲QEMU/KVM提供的一個分佈式存儲系統。
  • OpenStack Block Storage (Cinder)
  • OpenStack Object Storage (Swift)
  • HTTP:可以使用英特網上的http服務獲取鏡像。這種方式只能只讀。
  • VMware ESX/ESXi or vCenter。

具體使用哪種 backend,可以在 /etc/glance/glance-api.conf 中配置。

 

glance主要幹嘛的講了,咱們又接着講創建虛擬機時glance幹了點啥。

我們展開glance這部分可以看到,她內部的邏輯如下,從上面的圖1我們可以知道,創建虛擬機的時候是compute的nova組件向glance發起請求,因爲創建虛擬機需要從glance獲取鏡像。(圖三省略了組件之間通信經過RabbitMQ的過程)

ps:所以這裏結合咱們的圖,就可以看出來了,咱們虛擬機創建需要的涉及獲取鏡像元數據和獲取鏡像本身,而不是直接一步到位從存儲直接拿,沒有元數據拿不到地址信息沒辦法取。

大致流程就是咱們這個圖3這樣的,下面咱們來串一串涉及的幾個主要的函數。

在此我插一句題外話,我滿網去搜從glance下載鏡像的文章材料的時候,出來的全是glance的介紹還有上傳鏡像創建鏡像的,下載鏡像的材料基本沒有,沒啥邏輯線參考,所以下面的內容就屬於我參考上傳還有註釋摸索的,正確性不保證啊,僅供參考。

 

繼續,從圖3我們可以看到,是計算節點的nova組件向glance發出了獲取鏡像請求,所以我們將從這裏切入去看源碼。#在openStack\nova-stable-queens\nova\compute\api.py中我們可以看到創建虛擬機的函數

ps:當然這並不是指在這個函數就完成了創建虛擬機的事情,openstack裏面很多地方用到了非純責任鏈模式。就是一個傳一個,搞得定的自己搞,搞不定的傳給下一個

_create_instance這個函數只是將創建虛擬機這個大任務,他所能完成的部分(向glance發出獲取虛擬機鏡像的請求)完成,不能完成的部分繼續交給他的下家

#創建虛擬機,由此函數compute節點向glance發出獲取鏡像請求

def _create_instance(self, context, instance_type,
               image_href, kernel_id, ramdisk_id,
               min_count, max_count,
               display_name, display_description,
               key_name, key_data, security_groups,
               availability_zone, user_data, metadata, injected_files,
               admin_password, access_ip_v4, access_ip_v6,
               requested_networks, config_drive,
               block_device_mapping, auto_disk_config, filter_properties,
               reservation_id=None, legacy_bdm=True, shutdown_terminate=False,
               check_server_group_quota=False, tags=None,
               supports_multiattach=False):

        #獲取鏡像源數據(包括image_id、)
        if image_href:
            image_id, boot_meta = self._get_image(context, image_href)
        else:
            image_id = None
            boot_meta = self._get_bdm_image_metadata(
                context, block_device_mapping, legacy_bdm)

        self._check_auto_disk_config(image=boot_meta,
                                     auto_disk_config=auto_disk_config)

        #檢查flavor配置與鏡像規格是否匹配
        self._checks_for_create_and_rebuild(context, image_id, boot_meta,
                instance_type, metadata, injected_files,
                block_device_mapping.root_bdm())


        return instances, reservation_id

 

_get_image()函數,調用了image_api.get去調用manager.py

    #獲取鏡像函數
    def _get_image(self, context, image_href):
        if not image_href:
            return None, {}
        #實例化一個compute API對象image_api調用manager
        image = self.image_api.get(context, image_href)
        return image['id'], image

這個image_api不是憑空冒出來的,是實例化了一個compute  API類 image_api

@profiler.trace_cls("compute_api")
class API(base.Base):
    """API for interacting with the compute manager."""

    def __init__(self, image_api=None, network_api=None, volume_api=None,
                 security_group_api=None, **kwargs):
        self.image_api = image_api or image.API()

在openStack\nova-stable-queens\nova\compute\manager.py中,_build_and_run_instance函數會調用objects.ImageMeta.from_dict(image)獲取image元數據

def _build_and_run_instance(self, context, instance, image, injected_files,
        admin_password, requested_networks, security_groups,
        block_device_mapping, node, limits, filter_properties,
        request_spec=None):

    # 獲取鏡像名
    image_name = image.get('name')

    self._check_device_tagging(requested_networks, block_device_mapping)

            #獲取鏡像元數據
            image_meta = objects.ImageMeta.from_dict(image)

 

從此compute節點的nova組件,正式將消息發給glance。在openStack\glance-stable-queens\glance\api\v1\router.py裏面,請求被glance.api.v1.router:API的對象處理。

然後我們看一下router.py文件的API類。它繼承了wsgi.Router,可以根據REST請求的URL以及請求的類型(GET、PUT等)將請求映射給不同對象的不同方法進行處理。

class API(wsgi.Router):

    """WSGI router for Glance v1 API requests."""

    def __init__(self, mapper):
        reject_method_resource = wsgi.Resource(wsgi.RejectMethodController())

 

然後wsgi開始請求映射,請求最終都是被images.py、image_data.py、schemas.py、image_access.py以及image_tags.py中的controller類的不同方法處理。 所以上面下載鏡像的請求會被glance/api/v1/images.py中的Controller類的index()函數處理

        images_resource = images.create_resource()

        mapper.connect("/",
                       controller=images_resource,
                       action="index")
        mapper.connect("/images",
                       controller=images_resource,
                       action='index',
                       conditions={'method': ['GET']})
        mapper.connect("/images",
                       controller=images_resource,
                       action='create',
                       conditions={'method': ['POST']})
        mapper.connect("/images",
                       controller=reject_method_resource,
                       action='reject',
                       allowed_methods='GET, POST')
        mapper.connect("/images/detail",
                       controller=images_resource,
                       action='detail',
                       conditions={'method': ['GET', 'HEAD']})

 

根據圖3我們可以看到,glance-registry要做的事情是向數據庫去查詢合適的鏡像,Controller類的index()函數接到了這個任務,我們可以由註釋看到這是在查詢鏡像列表,通過get_images_list函數,從數據庫獲取image元數據

    def index(self, req):
        """
        Returns the following information for all public, available images:

            * id -- The opaque image identifier
            * name -- The name of the image
            * disk_format -- The disk image format
            * container_format -- The "container" format of the image
            * checksum -- MD5 checksum of the image data
            * size -- Size of image data in bytes

        :param req: The WSGI/Webob Request object
        :returns: The response body is a mapping of the following form

        ::

            {'images': [
                {'id': <ID>,
                 'name': <NAME>,
                 'disk_format': <DISK_FORMAT>,
                 'container_format': <DISK_FORMAT>,
                 'checksum': <CHECKSUM>,
                 'size': <SIZE>}, {...}]
            }

        """
        self._enforce(req, 'get_images')
        params = self._get_query_params(req)
        try:
            images = registry.get_images_list(req.context, **params)
        except exception.Invalid as e:
            raise HTTPBadRequest(explanation=e.msg, request=req)

        return dict(images=images)

 

這裏會返回一堆image的元數據,我們可以根據拿到的這些image檢出需要的鏡像信息, 然後去後端存儲裏面下載真正我們需要的鏡像,下載鏡像的操作又從openStack\glance-stable-queens\glance\api\v1\images.py文件裏的def show開始分爲4步

所以在 def show() 中我們可以看到

1. 先調用image_meta = self.get_active_image_meta_or_error(req, id)獲得image_meta

image_meta = self.get_active_image_meta_or_error(req, id)

 

2.然後確認image_meta.get('size') 不等於0,調用 self._get_from_store 獲取 該 image的數據

     def show(self, req, id):
        """
        Returns an iterator that can be used to retrieve an image's
        data along with the image metadata.

        :param req: The WSGI/Webob Request object
        :param id: The opaque image identifier

        :raises HTTPNotFound: if image is not available to user
        """

        self._enforce(req, 'get_image')

        try:
            image_meta = self.get_active_image_meta_or_error(req, id)
        except HTTPNotFound:
            # provision for backward-compatibility breaking issue
            # catch the 404 exception and raise it after enforcing
            # the policy
            with excutils.save_and_reraise_exception():
                self._enforce(req, 'download_image')
        else:
            target = utils.create_mashup_dict(image_meta)
            self._enforce(req, 'download_image', target=target)

        self._enforce_read_protected_props(image_meta, req)

        if image_meta.get('size') == 0:
            image_iterator = iter([])
        else:
            #通過_get_from_store獲取image元數據
            image_iterator, size = self._get_from_store(req.context,
                                                        image_meta['location'])
            image_iterator = utils.cooperative_iter(image_iterator)
            image_meta['size'] = size or image_meta['size']
        image_meta = redact_loc(image_meta)
        return {
            'image_iterator': image_iterator,
            'image_meta': image_meta,
        }

 

拿到我們需要的這個image id 我們就可以確定出唯一的一個image,但是這並不能直接從後端存儲裏面下載鏡像,還需要用這個id去查到這個image的地址,從後端存儲下載鏡像還需要store,如圖

 

2.在 _get_from_store()函數中,又調用 glance_store.location.get_location_from_uri, 從 image uri 中獲取 location,存入loc

這個glance-store 是images.py開始就導入的,glance-store 向 glance-api 提供文件 backend.py 作爲 store 操作的統一入口

"""
/images endpoint for Glance v1 API
"""

import copy

import glance_store as store
import glance_store.location

 

3.調用get_store_from_uri, 獲取所用的 store

4.通過store調用 store.get 獲取 image data,最後返回鏡像。就此完成了鏡像下載操作

這裏的store實際上是一個image_factory對象,在glance中,一般通過wsgi通過分發dispatch的請求,會生成一個image_factory對象和一個image_repo對象,image_factory對應後端store進行鏡像的存儲等操作

    def _get_from_store(context, where, dest=None):
        try
            #調用glance_store.location.get_location_from_uri,
            #從 image uri 中獲取 location
            loc = glance_store.location.get_location_from_uri(where)

            #調用get_store_from_uri獲取所需的image對象
            src_store = store.get_store_from_uri(where)

            if dest is not None:
                src_store.READ_CHUNKSIZE = dest.WRITE_CHUNKSIZE
            #調用src_store.get獲取鏡像image對象的image_data和image_size數據
            image_data, image_size = src_store.get(loc, context=context)

        except store.RemoteServiceUnavailable as e:
            raise HTTPServiceUnavailable(explanation=e.msg)
        except store.NotFound as e:
            raise HTTPNotFound(explanation=e.msg)
        except (store.StoreGetNotSupported,
                store.StoreRandomGetNotSupported,
                store.UnknownScheme) as e:
            raise HTTPBadRequest(explanation=e.msg)
        image_size = int(image_size) if image_size else None
        #返回鏡像image_data以及鏡像的size
        return image_data, image_size

 

ps:get_store_from_uri和store.get 這兩個函數都無法進一步點進去看了,openstack官網文檔裏表示glance-store這部分代碼已經提取出去了

但是他留下了函數說明:get_store_from_uri()通過給定一個URI,返回存儲對象

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章