源码版本: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      ---------------dashboard【2】

|---instances --------------panel【3】

|---panel.py----------------------负责往dashboard中注册panel

|---urls.py

|---views.py

...

|---dashboard.py --------------负责往Horizon中注册dashboard

|---models.py

...

  Horizon项目架构:主要分为两个部分:horizon和openstack_dashboard。horizon提供库和功能组件,openstack_dashboard是一个使用了horizon的Django项目。  

  说明:这里只列出了和本次分析有关的部分目录和文件。【1】还有一个openstack_dashboard/local/local_settings.py,其为horizon作为openstack组件的组件配置文件,此处的settings.py为Django项目带的配置文件。【2】【3】这里的dashboard和panel是horizon自定义的组件,对应页面上的相应部分。具体如下图所示:

2、URL绑定分析:

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。

3、Site、Dashboard、Panel三者的加载

  这里先说明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()【】   ...   # 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源码分析(二)

horizon源码分析(一)的更多相关文章

  1. openstack之horizon源码分析

    一.基础准备: Horizon是基于django webframework开发的标准的Python wsgi程序,django的设计专注于代码的高度可重用,信奉DRY原则,一切面向对象,而Horizo ...

  2. horizon源码分析(二)

    源码版本:H版 一.简要回顾 对于请求: 地址:/dashboard/admin/instances/ 方式:POST 参数: instances_filter_q: action:instances ...

  3. openstack之horizon源码分析之二

    一.概述: django基础入手: django新建project:#django-admin startproject mysite 生成如下目录: mysite ├── manage.py └── ...

  4. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  5. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  6. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  7. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  8. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  9. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

随机推荐

  1. React 初学

    React.createClass({}); getInitialState,this.setState({}); {}解读代码块,外层不要加引号,比如onChange={this.handleCha ...

  2. 第八次作业psp

    psp 进度条 代码累积折线图 博文累积折线图 psp饼状图

  3. c# Application.run和form.show区别

    Application.run(form):在当前线程上开始运行标准应用程序消息循环,并使指定窗体可见. form.show() :使指定窗体可见: 参照:https://blog.csdn.net/ ...

  4. 对其中的一个特点将NABC的分析结果

    一.题目要求 每一个组员针对其中的一个特点将NABC的分析结果发表博客上(截止日期4月8日晚24:00前). 二.分析结果 特点之一:通讯方便 <渴了么>这个安卓APP特点之一就是通讯方便 ...

  5. 让程序运行更加面向用户——电梯V2.1

    电梯V2.1 GitHub仓库地址 Problem 为程序添加命令行参数(自行利用搜索引擎进行学习). 写成 .cpp .h 文件分离的形式(大多数同学已经达到). 继续完善函数分离.模块化思想. 要 ...

  6. 福大软工1816 - 404 Note Found选题报告

    目录 NABCD分析引用 N(Need,需求): A(Approach,做法): B(Benefit,好处): C(Competitors,竞争): D(Delivery,交付): 初期 中期 个人贡 ...

  7. web登录密码加密

    文章:如何实现登录页面密码加密 文章:用RSA加密实现Web登录密码加密传输 文章:web登录用户名密码加密 知乎文章:Web前端密码加密是否有意义? 文章:记录一次黑客模拟攻击 成功拿到淘宝账号和密 ...

  8. Linux的压缩/解压缩文件处理 zip & unzip

    Linux的压缩/解压缩命令详解及实例 压缩服务器上当前目录的内容为xxx.zip文件 zip -r xxx.zip ./* 解压zip文件到当前目录 unzip filename.zip 另:有些服 ...

  9. js获取窗口滚动条高度、窗口可视范围高度、文档实际内容高度、滚动条离浏览器底部的高度

    1.获取窗口可视范围的高度 //获取窗口可视范围的高度 function getClientHeight(){ var clientHeight=0; if(document.body.clientH ...

  10. 成功解决JSP和Servlet的中文乱码问题

    表单提交时出现乱码: 在进行表单提交的时候,经常提交一些中文,自然就避免不了出现中文乱码的情况,对于表单来说有两种提交方式:get和post提交方式.所以请求的时候便有get请求和post请求.以前我 ...