Horizon 原始碼閱讀(二)—— Horizon 模組註冊機制
一、寫在前面
這篇文章主要介紹一下OpenStack Horizon — juno專案模組註冊機制,本文將通過對Horizon原始碼解析瞭解各個模組註冊載入機制。全文穿插了Horizon組建的程式碼塊以及個人理解註釋,由於能力和時間有限,錯誤之處在所難免,歡迎指正!
如果轉載,請保留作者資訊。
郵箱地址:[email protected]
二、Horizon模組註冊機制
Horizon/base.py中實現了一套dashboard/panel註冊、動態載入機制,使得Horizon面板上所有的dashboard都是”可插拔”的,所有的panel都是”動態載入”的,在以下的內容裡將通過原始碼解析具體分析horizon/base.py檔案,揭開這神祕的面紗。
流程圖:
整個Horizon模組的載入流程如上圖所示,horizon專案在引入的時候自動通過horizon.__init__()自動Load類屬性方法def_lazy_urls(self),載入LazyURLPattern(url_patterns)其中url_patterns是一個巢狀方法,LazyURLPattern繼承自Django SimpleLazyObject類,個人理解這是把_lazy_urls()方法轉換成一個懶惰方法,這裡只加載不執行,等正真用到的時候在執行。
當用戶通過Request請求訪問的時候,根據Django Urls對映機制,會去openstack_dashboard.urls.py匹配,urls.py檔案中url(
注意點:
1、settings.INSTALLED_APPS在這裡拉出來講解一下,settings.INSTALLED_APPS是一個字串tuple ,內容是專案中引入的應用,在Horizon專案中有兩部分組成:
第一部分:settings.py
horizon(2-1).py
1 2 3 4 5 6 7 8 9 10 11 12INSTALLED_APPS = ['openstack_dashboard', 'django.contrib.contenttypes', 'django.contrib.auth', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.humanize', 'django_pyscss', 'openstack_dashboard.django_pyscss_fix', 'compressor', 'horizon', 'openstack_auth',] 第二部分:settings.py
horizon(2-2).py
1 2 3 4 5 6 7 8 9 10# Load the pluggable dashboard settingsimport openstack_dashboard.enabledimport openstack_dashboard.local.enabledfrom openstack_dashboard.utils import settingsINSTALLED_APPS = list(INSTALLED_APPS) # Make sure it's mutablesettings.update_dashboards([ openstack_dashboard.enabled, openstack_dashboard.local.enabled,], HORIZON_CONFIG, INSTALLED_APPS) openstack_dashboard.enabled/openstack_dashboard.local.enabled:配置啟用和顯示Daishboard,用來配置Horizon左側導航顯示的dashboard。自己開發一個dashboard需要在openstack_dashboard/enabled 新增一個類似 _50_mydashboard.py檔案,這樣才會被注入到settings.INSTALLED_APPS配置項中。整個settings.INSTALLED_APPS中引入的APP由這兩部分組拼在一起。
2、每個dashboard模組下都有一個dasbboard.py檔案裡面定義了屬於當前dashboard的PanelGroup,和各個PanelGroup下的Panel,在Horizon模組被匯入的時候會去依次遍歷Dasboard→PanelGroup→Panel,所有的dashboard註冊到Horizon名稱空間下,各個panel註冊到自己的dashboard名稱空間下。
三、Horizon模組註冊原始碼解析
接下去剛通過分析Juno 中Horizon關於這部分的原碼來講解。
細粒度的資料流圖:
上圖是關於Horizon中關於模組註冊的資料流圖。
1)horizon匯入
settings.py-> 匯入horizon
INSTALLED_APPS = [
‘horizon',
……
]
Python中在匯入一個包時,實際上匯入了它的__init__.py檔案,當我們匯入Horizon這個包的時候,__init__.py檔案自動執行,在__init__.py 檔案中再匯入其他的包,或者模組。其中在horizon包的__init__.py檔案中:
from horizon.baseimport Horizon # noqa
……
urls = Horizon._lazy_urls
……
匯入了Horizon.base._lazy_urls方法,_lazy_urls()是完成整個模組註冊的入口,Horizon.base._lazy_urls方法增加了@property, @property 可以將python定義的函式“當做”屬性訪問。__init__.py將Horizon.base._lazy_urls匯入的時候自動執行該方法體。
注:horizon提供的Site類、Dashboard類、Panel類,負責整個基本架構,實現單例的設計模式,全域性只有一個只有一個horizon物件。 對於這個檔案程式碼架構的分析放到以後。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@propertydef _lazy_urls(self): """ 延遲載入URL模式。 這種方法避免試圖載入URLconf值之前設定模組已經載入的問題。 """ def url_patterns(): return self._urls()[0] # LazyURLPattern(url_patterns) 猜測這是一個懶惰方法,load的時候把方法傳入,當需要使用的使用才執行方法。 # request 請求進來的時候,回去Openstack.urls.py 進行配置,include('openstack_auth.urls')這個時候會執行url_patterns() # self.namespace = "horizon" # self.slug = "horizon" return LazyURLPattern(url_patterns), self.namespace, self.slug |
其中LazyURLPattern(url_patterns)沒有繼續執行下去,LazyURLPattern()繼承自Djano SimpleLazyObject理解成將一個方法轉換成延遲執行的方法,只有正真呼叫的時候才會正真執行,顯然這裡沒有正真呼叫,這裡只是一個載入,url_patterns作為一個方法引用當作引數。
2)ruquest請求
當用戶通過瀏覽器發起request請求,根據Django的框架結構,使用URLconf進行連線請求和後端處理(view)的繫結,使用view進行後端處理,使用template進行頁面渲染。
1 2 3 4 5 6 7 8 9 10 11 |
urlpatterns = patterns( '', # 匹配網站根目錄的URL,對映到openstack_dashboard.views.splash檢視。 url(r'^$', 'openstack_dashboard.views.splash', name='splash'), # 任何以/auth/開頭的URL將會匹配,引入openstack_auth.urls url(r'^auth/', include('openstack_auth.urls')), # 任何以/api/開頭的URL將會匹配,引入openstack_dashboard.api.rest.urls url(r'^api/', include('openstack_dashboard.api.rest.urls')), # 任何訪問URL將會匹配,都引用horizon.urls url(r'', include(horizon.urls)), ) |
openstack_dashboard.urls.py:
url(r'', include(horizon.urls)): horizon.urls對應的是horizon.baise._lazy_urls(),執行之前匯入horizon包載入_lazy_urls()方法中的
def url_patterns():
return self._urls()[0]
接著呼叫 horizon.base.Site._urls(),完成所有Dashboard和Panel的註冊變編譯URLconf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
def _urls(self): """ 從註冊的Dashboards構造Horizon的Urlconf """ # 獲取horizon.site_urls urlpatterns 值 urlpatterns = self._get_default_urlpatterns() # 從“settings.INSTALLED_APPS發現模組,包含dashboard.py、panel.py的模組註冊,新增到登錄檔中self._registry,沒有的丟擲異常。 self._autodiscover()--------------------標註(1) # 發現每個Dashboards裡的Panels # 從登錄檔self._registry 取出註冊dashboard,註冊每個dashboard中的panel # self._registry:{<class 'openstack_dashboard.dashboards.identity.dashboard.Identity'>: <Dashboard: identity>, # <class 'openstack_dashboard.dashboards.project.dashboard.Project'>: <Dashboard: project>, # <class 'openstack_dashboard.dashboards.admin.dashboard.Admin'>: <Dashboard: admin>, # <class 'openstack_dashboard.dashboards.settings.dashboard.Settings'>: <Dashboard: settings>} #self._registry.values():[<Dashboard: identity>, <Dashboard: project>, <Dashboard: admin>, <Dashboard: settings>] for dash in self._registry.values(): dash._autodiscover() -----------------標註(2) # 載入基於外掛的面板配置 self._load_panel_customization() # 允許覆蓋模組 if self._conf.get("customization_module", None): customization_module = self._conf["customization_module"] bits = customization_module.split('.') mod_name = bits.pop() package = '.'.join(bits) mod = import_module(package) try: before_import_registry = copy.copy(self._registry) import_module('%s.%s' % (package, mod_name)) except Exception: self._registry = before_import_registry if module_has_submodule(mod, mod_name): raise # 編譯動態URL配置。 for dash in self._registry.values(): urlpatterns += patterns('', url(r'^%s/' % dash.slug, include(dash._decorated_urls))) # 返回三個引數,django.conf.urls.include “"" 返回的urlpattern引數值 ([<RegexURLPattern user_home ^home/$>, <RegexURLPattern jsi18n ^i18n/js/(?P<packages>\S+?)/$>, <RegexURLPattern set_language ^i18n/setlang/$>, <RegexURLResolver <module 'django.conf.urls.i18n' from '/usr/lib/python2.7/dist-packages/django/conf/urls/i18n.pyc'> (None:None) ^i18n/>, <RegexURLResolver <RegexURLResolver list> (settings:settings) ^settings/>, <RegexURLResolver <RegexURLResolver list> (identity:identity) ^identity/>, <RegexURLResolver <RegexURLResolver list> (project:project) ^project/>, <RegexURLResolver <RegexURLResolver list> (admin:admin) ^admin/>, <RegexURLResolver <RegexURLResolver list> (settings:settings) ^settings/>, <RegexURLResolver <RegexURLResolver list> (identity:identity) ^identity/>, <RegexURLResolver <RegexURLResolver list> (project:project) ^project/>, <RegexURLResolver <RegexURLResolver list> (admin:admin) ^admin/>], 'horizon', 'horizon') """ return urlpatterns, self.namespace, self.slug |
標記(1)從“settings.INSTALLED_APPS發現模組,包含dashboard.py、panel.py的模組註冊,新增到登錄檔中self._registry,沒有的丟擲異常。
horizon.Site._autodiscover():