horizon is easy and complex

Horizon Is Easy, Horizon Is Complex

本文出自我的同事兼基友@monsterxx03 之手,本人稍作潤色

 

Horizon Is Easy, Horizon Is Complex

如果要用一句話來概括Openstack Dashboard項目Horizon:它是一個基於django webframework開發的標準的python wsgi程序。如果要再加一句廢話,它一般會運行在webserver(apache/nginx)上。

 

Why Is It Easy?

 

說Horizon簡單,是指它的部署架構簡單,就是個單純的基於django的網站,後臺甚至沒有使用數據庫。不像Swift,Nova這些項目,有非常多的組件(xxx-server,xxx-api…),理清楚各個組件之間的關係,數據流的走向都要費一番功夫。Horizon用到的技術也比較傳統,沒有異步非阻塞,消息隊列,websocket這些比較時髦的東西(目前沒有),前端也只是bootstrap+jquery+less,沒有什麼很前衛的東西。

用戶從登陸到看到虛擬機列表的這個過程的數據流走向:

Why Is It Complex?

Horizon的複雜性分成兩方面看:

  1. Horizon需要和幾乎所有的OpenStack service api打交道, 而且各個服務可能就還有多個api版本,api多版本的兼容不是都能在client裏完成的(比如Keystone),Horizon還需要針對各個api的多版本做兼容。
  2. Horizon代碼本身很複雜,抽象,利用了django相當多的高級特性,可以說把oop的概念運用到了極致,但這也爲過度自定製Horizon帶來了麻煩。
 

Design Philosophy

Horizon是基於django開發的,要說Horizon的設計,不得不說django,

Django focuses on automating as much as possible and adheres to the DRY(Don't Repat Yourself) principle.

django的設計專注於代碼的高度可重用,信奉DRY原則,一切面向對象,而Horizon可以說高度match了django的設計風格,good or bad就見仁見智了。

先說一點web開發的常識,網站程序基本有三部分組成,業務邏輯代碼(Python),靜態文件(js/css),模板(Python中的jinja,mako,nodejs中有jade), 用戶向webserver發起請求之後,server程序找到當前url對應的模板,填充模板變量(輸出成字符串形式的html源碼),返回給瀏覽器,瀏覽器渲染頁面。

如果是簡單的程序,一個頁面就對應一個模板,非常簡單。一般一個網站有自己的統一風格,所有頁面都會有一些相同的元素,每個頁面單獨寫一遍很累贅,也不好維護,所以一般模板語言都有繼承(extend),插入(include)等特性,來提高頁面的複用率。

Horizon做得就更徹底一些,它將頁面上所有元素模塊化,網頁中一些常見元素,表單,表格,標籤頁,全部封裝成Python類,每個組件有自己對應的一小塊html模板.當渲染整個頁面的時候,Horizon先找到當前頁面有多少組件,將各個組件分別進行渲染變成一段html片段,最後拼裝成一個完整的html頁面,返回瀏覽器。

簡單總結Horizon的特點:

  1. 頁面元素模塊化
  2. 子面板可插拔
  3. All in One(從部署上來說,Horizon只有它自己這一個組件)
 

Dive into Horizon

 

Some Concepts

Horizon這套面板的設計分成三層:Dashboard → PanelGroup → Panel

Horizon中現有的dashboard有4個:

  1. project 普通用戶登陸後看到的項目面板
  2. admin 管理登陸後可見,左側的管理員面板
  3. settings 右上角的設置面板,裏面可設置語言,時區,更改密碼
  4. router(配置文件中將profile_support打開可見),ciso nexus 1000v的管理面板

每一個dashboard都是django中的一個app,django中的app可以理解成對業務邏輯模塊化的一種手段,裏面可以包含自己獨有的url設定,模板,和業務邏輯代碼.

每個dashboard下定義了一系列的PanelGroup,虛擬機管理對應到界面上就是一個PanelGroup(Manage Compute), 裏面有一系列的子panel(Overview, Instances, Volumes…)。Swift,heat,neutron的管理面板各自都是一個PanelGroup,底下有各自的子panel.

Some Code

Horizon的源碼中,包含兩個代碼文件夾

  1. horizon
  2. openstack_dashboard

Horizon這個包是一些在django基礎上寫的通用組件,表格(table),標籤頁(tab),表單(form),麪包屑導航(browser),工作流(workflow),這些代碼和openstack的具體業務邏輯沒有什麼關係,如果做一個新的django項目,理論上可以複用Horizon這個包中的代碼。horizon/base.py中還實現了一套dashboard/panel機制,使得Horizon面板上所有的dashboard都是”可插拔”的,所有的panel都是”動態加載”的。

openstack_dashboard/dashboards/中是各個面板的具體實現代碼,其中包括各個面板的模板文件, 和後端service交互的業務邏輯代碼等。

 
 

FAQ

 

Horizon怎麼實現dashboard的可插拔?

 

之前說過,Horizon中的dashboard就是django的app,在openstack_dashboard/settings.py中的INSTALLED_APP變量定義了目前已有的四個dashboard:

  1. openstack_dashboard.dashboards.project
  2. openstack_dashboard.dashboards.admin
  3. openstack_dashboard.dashboards.settings
  4. openstack_dashboard.dashboards.router

如果自己按照Horizon自定製dashboard的流程寫了新的dashboard也需要加到這個配置文件中,新面板就能正常顯示。

 

每個dasboard是怎麼找到自己擁有的panel的?

 

每個dashboard模塊下都有一個dasbboard.py文件裏面定義了屬於當前dashboard的PanelGroup,和各個PanelGroup下的Panel,在Horizon模塊被導入的時候會去依次遍歷Dasboard→PanelGroup→Panel,所有的dashboard註冊到Horizon命名空間下,各個panel註冊到自己的dashboard命名空間下。

 

每個panel是怎麼找到自己的模板的?

 

這需要理解django中的template loader概念,簡單的說,template loader就是一個處理request的視圖尋找自己的模板文件路徑的方法,django自帶了好幾種template加載機制,Horizon用到了filesystem,app_directories這兩種,另外自己自定義了一種(horizon.loaders.TemplateLoader),loader的定義在openstack_dashboard.settings中的TEMPLATED_LOADERS變量。

  TEMPLATED_LOADERS = (
      'django.template.loaders.filesystem.Loader',
      'django.template.loaders.app_directories.Loader',
      'horizon.loaders.TemplateLoader',
  )

舉例:

當點擊左側project(這是個dashboard)下的instance(這是個panel)標籤時,頁面會跳轉到http://localhost/project/instances這個url,panel在的視圖類中寫了template_name=“project/instances/index.html”,查找這個文件的順序是:

  1. 先找openstack_dashboard/templates
  2. 沒找到再找openstack_dashboard/dashboards/project/templates/,這個目錄不存在,還是沒找到。
  3. 最後找openstack_dashboard/dashboards/project/instances/templates, 找了index.html這個文件。

 

怎麼顯示swift的面板?怎麼顯示heat的面板?

 

這裏很好的體現了Horizon的動態特性,顯不顯示某panel,實際上並不由Horizon控制,而是由Keystone控制。一個後端的webservice需要集成進openstack的話,第一步是在Keyston處登記,在Keyston的service,endpoint這兩張表內寫入該service的的一些元數據信息(包括url地址)。

Horizon控制面板是否顯示用了一套叫permission的機制,比如Container(swift的panel)要顯示的條件是”openstack.services.object-store”。用戶的登陸後的token中又service catalog信息,catalog中必須包含object-store這個service,滿足,container這個panel就會註冊到project這個dashboard之下,頁面上就能顯示,如果Keyston中沒有swift的catalog信息,Horizon就不會註冊container這個panel,swift的面板就不會顯示。heat同理。

要了解permission的具體實現請看openstack_auth/backend.py 模塊

 

普通用戶看不到admin面板,這個是怎麼實現的?

 

還是用到了上個問題提到的permission,Admin dashboard這個類中寫的permission是”openstack.roles.admin”,則admin這個面板顯示的條件是當前登錄的用戶有admin這個role,滿足的用戶就能看到admin dashbaord.

 

Horizon處理登錄部分的代碼在哪?

 

參看openstack_auth部分

 

Customize Horizon 

 

自定義css

要對Horizon的css樣式做自定製,可以修改openstack_dashboard/static/less 目錄下的less文件。

 

自定義js

要對Horizon中js的行爲做定製,可以修改horizon/static/horizon/js/ 目錄下的文件,Horizon的js文件都是針對具體某個頁面組件做的行爲定製,比如form提交時的行爲,tab切換的行爲,所以都放在Horizon目錄下而不在openstack_dashboard目錄下。

 

添加新的dashboard和panel

Horizon自帶了兩條命令,方便快速得生成基礎代碼:

  1. `python manage.py startdash test_dash` 自動生成一個叫test_dash的dashboard,之後將test_dash寫入settings中的INSTALLED_APP中,Horizon就能加載此新面板
  2. `python manage.py startpanel test_panel -d openstack_dashboard.dashboards.admin –target auto` 會在Horizon的admin dashboard下生成一個叫test_panel的子面板
 

Event Driven Horizon

 

Horizon目前的實現是比較傳統的全刷新網站,openstack中有不少操作是異步的api,比如創建虛擬機,snapshot,volume等等。這些api被調用後資源會進入一個狀態遷移的過程,Horizon在前端頁面上實現狀態的實時刷新,用的是ajax輪訓的方式,這種方式效率比較低,也有延遲,目前也有bp要爲Horizon引進real time的特性,相關bp:

有一個實現草案:https://review.openstack.org/#/c/40198/1

原理是通過sock.io(python版基於tornado的ioloop實現)再寫一個websocket的server.同時Horizon後臺添加消息隊列支持,監聽openstack集羣中的消息,監聽到了自己需要的消息之後,通知websocket server,server再通知瀏覽器中的js.

不過這樣做有一些問題,server上需要躲開一個端口專門處理websocket請求,還需要一個daemon監聽消息隊列的消息,這會增加Horizon的部署複雜度。社區決定將real time的實現放到I版做,這個實現草案中將整個Horizon本身也跑在了tornado server上,這裏的性能問題有待商榷(主要是對eventlet的monkey_patch的原理有疑惑)。

 
 

吐槽

 

本來想寫基於Horizon開發會帶來的不便利的地方,想了一下,先寫些離題的東西。

首先噴一下django的DRY原則,很多程序員都追求自己的代碼裏沒有冗餘的代碼,希望系統像一個精密的機械每個齒輪都緊緊得咬合,沒有一個多餘的零件,也不要出現兩個相同的零件,這就是所謂的DRY(don't repeat yourself)。但是也有一句話流傳的很廣:“DRY有毒”。DRY是不是有毒,我覺得要看寫的是什麼程序,如果就是一個在後臺默默跑着的server,它和外部交互的協議都是定死的,那把接口實現之後無論怎麼重構代碼,追求架構上的DRY都不過分。

 

但網站這種項目,終端消費者是人而不是其他程序,它和外部交互的協議就是html頁面。人是最喜新厭舊的,所以會出現網站改版這種事,說白了網站程序它和外部的交互協議三天兩頭都有可能變化。當你費盡奇淫巧計,用上各種Design Pattern,OOP設計,最後老闆說:“那什麼,我想把這個頁面的按鈕做成大號的,而且點了之後框給我跳兩下再出來”,就開始鬱悶了,你和boss說,按照我的優良OOP設計,所有頁面上的按鈕都繼承自一個BaseButton,他們的動畫效果都是預先定義好的,按鈕點了之後對話框能從上下左右出來,就是不能跳兩下再出來,可以改,但是會破壞我原本的優良設計。喂喂,根本沒人會來理你,看頁面的人在乎的只是這個按鈕而已。

 

可能你會說,這個例子裏,我設計的按鈕類不夠強大,它應該寫的更加抽象,子類的行爲要更加靈活。但要知道,程序員能實現的架構的複雜度與層次性和個人的經驗,項目所給的時間有相當大的關係。

說白了,就是水平不夠,偏要去追求什麼沒有一行重複代碼這種事情,而且給的時間只有個把月,最後的代碼很DRY但是不夠靈活,只會讓自己陷入困境.把握程序的優雅與靈活之間的平衡是一件很難的事情,有時候要量力而行。

再回到Horizon這個項目,讓我評價Horizon的代碼架構本身(去除一些pep8的小問題,我個人並不是很待見pep8),我的評價是Excellent!反正在一些開源的基於django的程序的實現裏,見過代碼行數更多的,但沒見過代碼寫的更漂亮的,至少是沒見過比它用django用的更牛的。

 

但是Horizon也不是一開始就是這個架構,我記憶裏E版的Horizon裏那個DataTable的實現真的是百轉千回,死活看不懂。現在在H版裏的實現將DataTable這個組件進行了很詳細的拆分,仍舊很複雜,但比以前的實現要清楚的多。

我曾經將Horizon的commit記錄reset到第一條,看過它早期的一些實現變化過程,它早期的代碼那叫個簡陋,各種髒,各種修修補補,最早的commit記錄是2011年1月,一直到現在2013年10月,將近3年的開發時間,一般公司,如果說我們做個面板,做個三年,最後做出來長Horizon這樣,那肯定會有人跳出來說:“丫我拿php寫一個,1個禮拜就能搞定,他媽你搞上三年,php是最好的語言,php萬歲!”.

但Horizon的的價值對一般人來說在openstack api的可視化體現,對開發人員來說是優秀代碼的學習範例,如果你用一種傳統的方式去開發一個openstack面板,他可能可以把前端做的很炫,讓Horizon顯得很土鱉,但代碼100%比不上Horizon,我的意見是你用django開發,後臺寫成Horizon這樣,基本可以說是優秀到頭了。

 



---我是低調的不顯眼的簡潔的不會被敵人發現的分割線---



     This article Powered by 努力牛皮

     新手上路,不恰之處,懇請指出,不勝感謝

   轉載請註明出處一塊努力的牛皮糖”:http://www.cnblogs.com/yuxc/

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