創建虛擬機是個很好的應用實例,因爲幾乎包括了所有主要組件的應用。我們可以看到虛擬創建流程圖如下,很複雜,所以進行拆解,我們今天主要看標紅這部分(下載鏡像)
在理創建虛擬機時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,返回存儲對象