該文同時發布在[ceph中國社區],署名為Thomas
在Openstack中創建云主機,首先得有鏡像,而Glance模塊提供鏡像服務功能,包括:鏡像的發現、檢索及存儲等,主要包括:glance-api和glance-registery兩個服務,分別負責鏡像的存儲和元數據管理。下面基于源碼,分析下鏡像的上傳進程。
先通過glance CLI上傳鏡像,來直觀的了解下鏡像上傳的進程:
# glance --debug image-create --file centos-7.0-x64-20g.qcow2 --disk-format raw --container-format bare --visibility public
通過在命令行中開啟--debug
,我們可以發現,上傳鏡像進程中glance CLI會發送以下3個不同的要求:
那這3個要求分別干了甚么事呢? 下面我們來逐一分解。
通過glance/api/v2/router.py.API
中定義的路由映照,我們知道上述的第1個要求由glance/api/v2/schemas.py.Controller.image
方法處理,以下:
#路由映照,代碼節選
#完全的函數實現,請參考glance/api/v2/router.py.API.__init__
def __init__(self, mapper):
#從/etc/glance/schema-image.json文件加載用戶定義屬性
custom_image_properties = images.load_custom_properties()
#創建glance/api/v2/schemas.py.Controller實例
schemas_resource =
schemas.create_resource(custom_image_properties)
#定義路由映照:
#curl -X GET /schemas/image -> shemas_resource.image
mapper.connect('/schemas/image',
controller=schemas_resource,
action='image',
conditions={'method': ['GET']})
#glance/api/v2/schemas.py.Controller.image
"""返回1個鏡像屬性字典,結構以下:
[
'name': image,
'properties':xxx
'additionalProperties':xxx
'definitions':xxx
'required':xxx
'links':xxx
]
"""字典值請看下面的分析進程
def image(self, req):
return self.image_schema.raw()
self.image_schema
在glance/api/v2/schemas.py.Controller.__init__
方法中初始化:
def __init__(self, custom_image_properties=None):
self.image_schema = images.get_schema(custom_image_properties)
它進1步調用glance/api/v2/images.py.ImagesController.get_schema
方法:
def get_schema(custom_properties=None):
#鏡像的基本屬性(是1個字典):id,name,status等的定義及描寫,
#通過glance CLI上傳鏡像成功后,這些字段信息會顯示在shell界面上
properties = get_base_properties()
"""1個包括3個元素的字典列表,像是名字映照束縛
[
{'rel': 'self', 'href': '{self}'},
{'rel': 'enclosure', 'href': '{file}'},
{'rel': 'describedby', 'href': '{schema}'},
]
"""
links = _get_base_links()
#根據配置/etc/glance/glance-api.conf決定是生成
#PermissiveSchema(默許)還是Schema,區分是PermissiveSchema
#多設置了links參數
if CONF.allow_additional_image_properties:
schema = glance.schema.PermissiveSchema('image', properties, links)
else:
schema = glance.schema.Schema('image', properties)
#合并用戶自定義屬性
#屬性合并很簡單:先得到兩個屬性集的交集,然后判斷交集的值是不是沖突
#如果值沖突,則拋異常,否則就合并數據集
if custom_properties:
for property_value in custom_properties.values():
property_value['is_base'] = False
schema.merge_properties(custom_properties)
return schema
最后來看看raw
方法,實現很簡單:調用父類的Schema
返回鏡像屬性字典,同時更新additionalProperties
屬性,然后將屬性字典返回給調用者
#glance/schema.py.PermissiveSchema
def raw(self):
raw = super(PermissiveSchema, self).raw()
raw['additionalProperties'] = {'type': 'string'}
return raw
#glance/schema.py.Schema
def raw(self):
raw = {
'name': self.name,
'properties': self.properties,
'additionalProperties': False,
}
if self.definitions:
raw['definitions'] = self.definitions
if self.required:
raw['required'] = self.required
if self.links:
raw['links'] = self.links
return raw
小結:第1個要求獲得了鏡像所支持的屬性字典定義,下面就該根據這些信息來驗證用戶輸入參數了。
下面來分析第2個要求的處理進程,根據上述的--debug
日志和路由映照,我們知道該要求由glance/api/v2/images.py.ImagesController.create
方法處理:
@utils.mutating
def create(self, req, image, extra_properties, tags):
"""函數實現比較簡潔,調用`gateway`的方法分別創建`image_factory`
和`image_repo`,后面的try{ }except代碼塊中,分別調用兩個對象的
`new_image`方法進行1系列的校驗和`add`方法添加數據庫條目,最后返
`image`對象給調用者;`gateway`在`ImagesController.__init__`方
法中初始化,是1個`glance/gateway.py.Gateway`實例,其他對象的
實例化也簡單明了,在這里不再表述。根據我們前述的glance CLI命令,
輸入的參數值以下:
req:是1個Request對象,包括該次要求的要求信息
image: 是1個字典,包括該次要求的屬性,以下:
{
'container_format': u'bare',
'disk_format': u'raw',
'visibility': u'public'
}
extra_properties: 擴大屬性字典,這里為空
tags:標簽,這里為空
"""
image_factory =
self.gateway.get_image_factory(req.context)
image_repo = self.gateway.get_repo(req.context)
#這里省略了,try{ }except異常處理
#實現用戶認證,策略檢查,參數檢查等,具體請看下面的分析
image =
image_factory.new_image(extra_properties=extra_properties,
tags=tags, **image)
#進1步實現相干的檢查,發送消息通知并記錄數據
image_repo.add(image)
return image
接著來看看get_image_factory
方法:
def get_image_factory(self, context):
"""根據下面的代碼實現,可以發現各對象間建立了1個調用鏈條:
返回最外層的對象給調用者,調用時逐級調用內部的方法完成相干的操作,
到達最里層對象后,又順著調用鏈逐級返回。其實,后面的`get_repo`方法
也是采取類似的實現,下面的分析中不再贅述。另外`self.store_api`,
`self.store_utils`,`self.db_api`等在`__init__`方法中實例化
進程很直接,這里就不再分析了。
"""
image_factory = glance.domain.ImageFactory()
store_image_factory = glance.location.ImageFactoryProxy(
image_factory, context, self.store_api,
self.store_utils)
quota_image_factory = glance.quota.ImageFactoryProxy(
store_image_factory, context, self.db_api,
self.store_utils)
policy_image_factory = policy.ImageFactoryProxy(
quota_image_factory, context, self.policy)
notifier_image_factory = glance.notifier.ImageFactoryProxy(
policy_image_factory, context, self.notifier)
#用戶可以通過`/etc/glance/glance-api.conf中的
#property_protection_file`選項配置屬性策略,默許為disabled
if property_utils.is_property_protection_enabled():
property_rules =
property_utils.PropertyRules(self.policy)
pif = property_protections.ProtectedImageFactoryProxy(
notifier_image_factory, context,
property_rules)
authorized_image_factory =
authorization.ImageFactoryProxy(
pif, context)
else:
authorized_image_factory =
authorization.ImageFactoryProxy(
notifier_image_factory, context)
return authorized_image_factory
根據上面的分析,我畫了下面的類圖:
可以看到*ImageFactoryProxy
類都繼承自glance/domain/proxy.py.ImageFactory
,通過類名也能夠猜出它的功能:鏡像工廠,那就是用來創建封裝鏡像對象的;各個子類也分別實現:權限檢查、消息通知、策略檢查、配額檢查等。
另外各個*ImageFactoryProxy
類都依賴于*ImageProxy
類。而各*ImageProxy
類都繼承自glance/domain/proxy.py.Image
,該類描寫的是鏡像的屬性信息,包括:name,image_id, status等。各*ImageProxy
類是對Image
的擴大。
各個類的關系弄清楚下了,下面來看看new_image
的實現:
#最外層的image_factory = #glance/api/authorization.py.ImageFactoryProxy, 作為調用入口
image =
image_factory.new_image(extra_properties=extra_properties,
tags=tags, **image)
為更直觀的展現new_image
的調用鏈,請看下面的序列圖:
序列圖中省略了各個
ImageFactoryProxy.new_image
方法中的其他處理進程。
可以看到new_image
調用從最左側的
glance/api/authorization.py/ImageFactoryProxy
1直到最深處(右數第3)的glance/domain/__init__.py/ImageFactory
,它返回1個domain/__init__.py.Image
對象,然后開始逐層往回
(location.py/ImageFactoryProxy
到authorization.py/ImageFactoryProxy
)調用
glance/domain/proxy.py/Helper.proxy
方法,最后的結果是:返回1個經過各個*ImageProxy
層層封裝的Image
對象,如所示(從里到外):
`__init__.py/Image` <- `location.py/ImagePorxy` <- ...... <- `authoriaztion.py/ImageProxy`
看到上面Image
的封裝進程,有無覺得很像TCP/IP協議棧的封包進程呢!有封包,就1定會有解包,下面1起來看看:
""""add方法就是用來解包和封包的
由上面的類圖2,我們知道:image_repo = authorization.py/ImageRepoProxy, 輸入參數image = authorization.py/ImageProxy
"""
image_repo.add(image)
看圖說話(類圖見上面的圖2):
咋1看,該序列圖和上面的很像吧!這就對了。可以看到add
調用從最左側的
glance/api/authorization.py/ImageRepoProxy
1直到最深處(右數2)的glance/db/__init__.py/ImageRepo
,首先調用unproxy
完成解包,然后記錄數據庫條目(這個時候可以在Dashboard上看到狀態為’已排隊’),接著開始逐層往回(location.py/ImageRepoProxy
到authorization.py/ImageRepoProxy
)調用
glance/domain/proxy.py/Helper.proxy
方法,和前述ImageFactoryProxy
類1樣,各ImageRepoProxy
類也順次完成 :權限檢查、屬性配額檢查、策略檢查和信息通知等,最后也是返回1個經過各個*ImageProxy
層層封裝的Image
對象,以供后續使用。
小結:經過上面的分析,我們知道上傳鏡像進程中的第2個要求,主要完成權限檢查,配額檢查,策略檢查,發布通知和記錄數據庫等操作。下面來看看鏡像文件的上傳進程。
根據前述glance CLI
的debug日志及路由映照,我們可以很容易找到上傳鏡像文件的入口:
#glance/api/v2/image_data.py/ImageDataController.upload
@utils.mutating
def upload(self, req, image_id, data, size):
"""
req:是1個Request對象,包括該次要求的詳細信息
image_id:在第2個要求進程中生成的鏡像id,
u'079ed99f-e5f6⑷9b1⑻6b4⑹95b56e26bd9'
data: 1個Input對象,用來控制后面的鏡像文件數據塊上傳
size:鏡像大小,由于命令行中沒有指定,這里為None
和前面1樣self.gateway在__init__方法中實例化,指向glance/
gateway.py/Gateway。get_repo方法返回和前述類圖2相同的對象
"""
image_repo = self.gateway.get_repo(req.context)
image = None
#省略try{ }except異常處理
#get方法與前述的add方法類似,首先從數據庫取出image_id指向的條目,
#封裝成`domain/__init__.py/Image`對象,然后經過層層封裝返回
#`authorization/ImageProxy`對象
image = image_repo.get(image_id)
#更新鏡像狀態為saving - ‘保存中’
image.status = 'saving'
#省略try{ }except異常處理
#save方法與上面的get方法1樣,逐層調用`ImageRepoProxy`完成相干的
#檢查,通知,最后更新數據庫條目狀態(這個時候可以在Dashboard上看
#到狀態為'保存中')
image_repo.save(image)
#和上面的save方法類似的處理方式,逐層調用`ImageProxy`的set_data
#,在該進程中會檢查用戶配額,發送通知,最后根據glance-api.conf文件
#中配置存儲后端上傳鏡像文件(通過add方法)到指定地方存儲
image.set_data(data, size)
#鏡像上傳成功后(在`location.py/set_data方法中上傳文件成功后,
#修改狀態為active),更新數據庫狀態為active(這個時候可以在
#Dashboard上看到狀態為'運行中'),終究的信息是這樣的:
"""
{
'status': 'active', 'name': None,
'checksum': 'd41d8cd98f00b204e9800998ecf8427e',
'created_at': datetime.datetime(2016, 6, 1, 2, 4, 32),
'disk_format': u'raw',
'locations': [{'url': 'rbd://1ee20ded-caae⑷19d⑼fe3-
5919f129cf55/images/079ed99f-e5f6⑷9b1⑻6b4-
695b56e26bd9/snap', 'status': 'active', 'metadata': {}}],
'properties': {},
'owner': u'25520b29dce346d38bc4b055c5ffbfcb',
'protected': False, 'min_ram': 0,
'container_format': u'bare', 'min_disk': 0,
'is_public': True, 'virtual_size': None,
'id': u'079ed99f-e5f6⑷9b1⑻6b4⑹95b56e26bd9', 'size': 0}
"""
image_repo.save(image, from_state='saving')
函數說明請查閱上面的注解,save
方法與add
方法的處理進程很類似,讀者可以根據前面add
的序列圖做進1步分析;下面只給出set_data
的序列圖:
至此,上傳鏡像的進程就分析完了。希望對大家有用。今天是61,在這里祝大家節日快樂!
上一篇 用pandas分析百萬電影數據
下一篇 Qt 獲取硬盤可用字節