horizon源碼分析(一)


源碼版本:H版

一、寫在前面

  本來應該搭建 horizon development 環境的,這樣方便 debug ,但是由於各種報錯,本人沒有搭建成功,這也導致有很多源碼疑問沒有解決,后續可以繼續補充這一部分。 官方搭建方法參考網址: http://docs.openstack.org/developer/horizon/quickstart.html
  源碼分析過程中使用的軟件如下:
SourceInsight :這個當然是源碼分析第一利器,可以進行全局關鍵字查找,非常方便。但是我目前發現其對 python 支持不是特別好,不過也可以湊合着用。
OneNote :雖然這個一個筆記軟件,但是由於其使用起來很像白板,所以可以像在紙上繪圖寫字一樣在里面分析函數的調用關系。
snagit :一個截圖軟件,可以對各個地方進行截圖,相當方便,截圖后粘貼到 OneNote 里面分析。
notepad ++ :用於在源碼分析過程中記錄筆記,不解釋
visio :用於繪制各種圖表    
  首先,我覺得源碼分析實在是一件體力活而非技術活,即便自己看不懂也可能是源碼作者自己寫得晦澀,況且現在開源社區越來越成熟,開源軟件本身為了推行使用,也會不斷增加說明文檔。所以,看源碼還是為了學習和模仿,說白了就是等某時候需要的時候腦子里可能閃現曾經見過別人這么做或者可以這么做。源碼分析的結果還是要看重實現原理和使用的技巧,具體細節實在沒有太多必要糾結!   
  這次分析 horizon 並非純粹為了看源碼,主要是分析一個前端頁面請求沒有響應的 bug ,帶着這個問題去梳理整個源碼分析流程! 本人初次接觸 python Django 以及 horizon ,還有很多東西不是很熟悉,還希望大家一起多交流,如有錯誤,歡迎批評指正!
  還是說一下我分析的思路,首先 horizon 本身基於 Django 實現,簡單地說就是網站的架構,主要分析前端請求和后端處理。所以第一步就是找到頁面請求的具體內容,這個我使用的是 chrome 的開發者工具;第二步是找到前端請求與后端處理的綁定,具體為此次動作由哪個函數來處理等等;第三步,可以料想 horizon 必然會調用 openstack 其他組件的 API 接口,這里只分析后端處理流程,止步於 API 調用,暫且不詳細分析。

 

二、horizon前端請求

  主要用瀏覽器 chrome 的開發者工具分析頁面發出的具體請求,此處是分析前端的一個 button 按鈕執行情況。
具體的 button 元素:

請求鏈接地址:

其他相關元素:

總結如下:
地址: /dashboard/admin/instances/ 1
方式: POST
參數:
instances_filter_q
action instances__soft_reboot__89a8849b-a3cd-4ce0-9158-c3dd69e8508e
說明: 1】請求地址中的/dashboard和openstack對Apache服務器的配置有關(這個可以看一下horizon的安裝配置過程),其將/設置為固定跳轉為/dashboard

 

三、URL后端綁定及APP的加載

根據Django的框架結構,使用URLconf文件(https://docs.djangoproject.com/en/1.4/topics/http/urls/)進行鏈接請求和后端處理(view)的綁定,使用view進行后端處理,使用template進行頁面渲染。

1、目錄結構:

horizon-------------組件,提供部分功能

   |---__init__.py-----------控制着horizon包導入行為

   |---base.py-----------horizon提供的Site類、Dashboard類、Panel類,負責整個基本架構

   |---site_urls.py

  

openstack_dashboard----------------網站project根目錄

   |---settings.py------------網站基本設置1

   |---urls.py----------------網站基本URL設置

   |---views.py---------------網站基本view

   |---templates--------------網站基本template

   |---dashboards

      |---admin      ---------------dashboard2

         |---instances --------------panel3

            |---panel.py----------------------負責往dashboard中注冊panel

            |---urls.py

            |---views.py

         ...

         |---dashboard.py --------------負責往Horizon中注冊dashboard

         |---models.py

      ...

  Horizon項目架構: 主要分為兩個部分: horizonopenstack_dashboard。 horizon提供庫和功能組件, openstack_dashboard 是一個使用了horizonDjango項目。   
  說明:這里只列出了和本次分析有關的部分目錄和文件。【 1 還有一個 openstack_dashboard/local/local_settings.py,其為horizon作為openstack組件的組件配置文件,此處的settings.py為Django項目帶的配置文件。【2】【3 】這里的 dashboard panel horizon 自定義的組件,對應頁面上的相應部分。具體如下圖所示:

 

2URL綁定分析:
openstack_dashboard/settings.py
ROOT_URLCONF = 'openstack_dashboard.urls'

openstack_dashboard/urls.py

  這里 使用了一個小trick,在導入horizon這個package時,進行如下處理:
horizon/__init__.py

  可見,在package的__init__.py中控制包的導入行為,這樣可以使包的使用更加簡潔方便。接着分析如下:
horizon/base.py
part1:

part2:

part3:
Site:

  綜上所述,最終的include()導入的是Site類的_lazy_urls。

 

3SiteDashboardPanel三者的加載

  這里先說明Site、Dashboard和Panel三者的加載過程以便后面的進一步分析。Horizon采用了注冊機制,即以一個對象為根對象,將其他組件注冊到根對象的屬性中,這種機制使得軟件的可擴展性更強,並且條理清晰,貌似像一種設計模式來着。Horizon是一個Site類對象,往其中注冊Dashboard類時會構建一個Dashboard對象注冊到其屬性_registry字典中;Panel類往Dashboard類中注冊,注冊時會構建Panel對象注冊到Dashboard對象的_registry字典里。具體情況如下:

3.1  一個dashboard注冊過程
以admin這個Dashboard為例:
openstack_dashboard/dashboards/admin/dashboard.py
import horizon

class SystemPanels(horizon.PanelGroup):
    slug = "admin"
    name = _("System Panel")
    panels = ('overview', 'metering', 'hypervisors', 'instances', 'volumes',

              'flavors', 'images', 'networks', 'routers', 'defaults', 'info')

class IdentityPanels(horizon.PanelGroup):
    slug = "identity"
    name = _("Identity Panel")
    panels = ('domains', 'projects', 'users', 'groups', 'roles')

 
class Admin(horizon.Dashboard):
    name = _("Admin")#用於display
    slug = "admin"#用於內部引用
    """panels可以用來發現與該Dashboard相關的所有Panel,以便在以后往Dashboard中注冊這些Panel"""
    panels = (SystemPanels, IdentityPanels)
    default_panel = 'overview'
    permissions = ('openstack.roles.admin',)
 
horizon.register(Admin)

horizon/base.py

Site類:
def register(self, dashboard):
  """Registers a :class:`~horizon.Dashboard` with Horizon."""
  return self._register(dashboard)
Registry類:
def _register(self, cls):
  if not inspect.isclass(cls):
    raise ValueError('Only classes may be registered.')
  elif not issubclass(cls, self._registerable_class):
    raise ValueError('Only %s classes or subclasses may be registered.'
                             % self._registerable_class.__name__)

  if cls not in self._registry:
    cls._registered_with = self
    self._registry[cls] = cls()

  return self._registry[cls]

  在horizon/base.py中的Horizon對象的_registy映射中添加了Dashboardà類實例的映射!

3.2  一個panel的注冊過程

admin這個Dashboard中的instancs為例
openstack_dashboard/dashboards/admin/instances/panel.py
import horizon
"""將admin這個Dashboard的dashboard.py導入"""
from openstack_dashboard.dashboards.admin import dashboard

class Aggregates(horizon.Panel):
    name = _("Host Aggregates")
    slug = 'aggregates'
    permissions = ('openstack.services.compute',)

"""在Dashboard為Admin中進行注冊"""
dashboard.Admin.register(Aggregates)

horizon/base.py

Dashboard類:
@classmethod
def register(cls, panel):

  """檢查cls是否已經注冊到Horizon對象中,並且在cls中注冊panel"""
  panel_class = Horizon.register_panel(cls, panel)
  panel_mod = import_module(panel.__module__)
  panel_dir = os.path.dirname(panel_mod.__file__)
  template_dir = os.path.join(panel_dir, "templates")
  if os.path.exists(template_dir):
    key = os.path.join(cls.slug, panel.slug)
    loaders.panel_template_dirs[key] = template_dir
  return panel_class  
  panel的注冊會先從Horizon對象中找出對應的已經注冊的Dashboard對象,然后在該Dashboard對象里面注冊Panel,注冊過程和Dashboard的注冊過程類似。
  
   繼續分析代碼流程如下:
horizon/base.py
Site 類:

Site類:
def _urls(self):
        
  """實際調用HorizonComponent._get_default_urlpatterns,獲取site_urls.py中的urlpatterns"""
  urlpatterns = self._get_default_urlpatterns()【1】
        
  """實際調用Site._autodiscover ,導入openstack_dashboard/settings.py中的HORIZON_CONFIG, INSTALLED_APPS配置的模塊中的dashboard.py和panel.py,此處會進行Dashboard的注冊"""
  """可參考文檔:http://docs.openstack.org/developer/horizon/topics/settings.html"""
  self._autodiscover()2】

  """發現每個Dashboard中的Panel,導入每個Panel的panel.py,從而注冊每個Panel"""
  for dash in self._registry.values(): 
    dash._autodiscover()3

  ...

  # Compile the dynamic urlconf.
  """dash為Dashboard類"""
  for dash in self._registry.values():
    urlpatterns += patterns('',
      url(r'^%s/' % dash.slug, include(dash._decorated_urls)))

  # Return the three arguments to django.conf.urls.include
  return urlpatterns, self.namespace, self.slug
Dashboard類:
@property
def _decorated_urls(self):

  urlpatterns = self._get_default_urlpatterns()【1
  default_panel = None
  # Add in each panel's views except for the default view.
  """panel為Panel類"""
  for panel in self._registry.values():
    if panel.slug == self.default_panel:
      default_panel = panel
      continue
    url_slug = panel.slug.replace('.', '/')
    urlpatterns += patterns('',
      url(r'^%s/' % url_slug, include(panel._decorated_urls)))
  ...   # Return the three arguments to django.conf.urls.include
  return urlpatterns, self.slug, self.slug 
Panel類:
@property
def _decorated_urls(self):
  """即查找panel的urls.py文件"""
  urlpatterns = self._get_default_urlpatterns()【1】
 
  #
Apply access controls to all views in the patterns   permissions = getattr(self, 'permissions', [])   _decorate_urlconf(urlpatterns, require_perms, permissions)   _decorate_urlconf(urlpatterns, _current_component, panel=self)   
  # Return the three arguments to django.conf.urls.include return urlpatterns, self.slug, self.slug
  標記處解釋如下:
1 HorizonComponent. _get_default_urlpatterns
  獲取對象的 urls 屬性指定的或對象所處的 package 下面的 urls.py 中的 urlpatterns
2 Site._autodiscover
def _autodiscover(self):
  """Discovers modules to register from ``settings.INSTALLED_APPS``.
  This makes sure that the appropriate modules get imported to register
  themselves with Horizon.
  """
  if not getattr(self, '_registerable_class', None):
    raise ImproperlyConfigured('You must set a '
                 '"_registerable_class" property '
                             'in order to use autodiscovery.')

  # Discover both dashboards and panels, in that order
  for mod_name in ('dashboard', 'panel'):
    """settings.INSTALLED_APPS感覺應該和openstack_dashboard/settings.py中的
    ORIZON_CONFIG, INSTALLED_APPS有關"""
    for app in settings.INSTALLED_APPS:
      mod = import_module(app)
      try:
        before_import_registry = copy.copy(self._registry)
        import_module('%s.%s' % (app, mod_name))
      except Exception:
        self._registry = before_import_registry
        if module_has_submodule(mod, mod_name):
          raise
  導入 settings.INSTALLED_APPS 中的各個模塊中的 dashboard.py panel.py ,舉例說就是 openstack_dashboard/dashboards 中各個 dashboard dashboard.py, 在導入 dashboard.py panel.py 時會執行相應的注冊行為
3 Dashboard._autodiscover
  導入當前 Dashboard 中的各個 Panel panel.py ,其中會進行 Panel 的注冊

4、結論如下:

對於請求:
  地址:/dashboard/admin/instances/
  方式:POST
  參數:
  instances_filter_q:
  action:instances__soft_reboot__89a8849b-a3cd-4ce0-9158-c3dd69e8508e
URL綁定為:
openstack_dashboard/dashboards/admin/instances/urls.py

 

 

>>>繼續看horizon源碼分析(二)

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM