文章目录
  1. 1. 分析环境搭建
  2. 2. 基于源码调试
  3. 3. Django框架
  4. 4. Horizon源码结构
  5. 5. 自定义Horizon

分析环境搭建

利用Devstack搭建一个用户开发的单节点OpenStack平台,具体搭建的教程在此不介绍。
平台最终效果图

基于源码调试

Ubuntu+Eclipse+pydev
调试环境
Horizon源码被修改之后, 到/opt/stack/horizon目录下执行指令 python manage.py runserver 10.82.82.241:8001从源码启动服务,访问Horizon界面。

Django框架

Django框架

  • Python Web框架,MVT架构
  • Models:数据模型
  • Views:控制用户所见
  • Templates:模板,用户真正所见的
    如上图所示,用户通过浏览器发送请求URL,urls.py文件会将不同的URL请求映射到不同的view中,views通过数据模型model请求数据库,在Horizon中,所有对其他组件的操作都是对于API的调用,所以在Horizon中不涉及数据库的访问。Views请求到数据库之后,会传入到Templates中,Templates中存放的是html文件,用来显示最终的界面给用户。
    下面是个人开发的基于Django框架的demo,代码结构以及最终界面如下:
    代码结构

    Horizon源码结构

    代码结构1
    如上图所示:Dashboard代码分两部分:openstack-dashboard和horizon。openstack-dashboard中存放了系统用到的所有资源文件、针对于当前界面新建的一些实例生成的view,Apache也是映射这部分代码;horizon是系统具体显示的代码实现,定义了一些公共的可重用的显示组件。
    代码结构2
    如上图所示:Horizon源码结构非常清晰,dashboards下面的每一个包都会对应到界面左侧导航栏的每一个panel group,展开每一个包会发现下面的每一个子包都会对应panel group中的一个panel。
    接下来以调整云主机大小为例分析Horizon源码结构,界面以及对应的源码结构如下图:
    代码结构3
    首先,在url.py中会存在对应的url映射:
    url.py
    接下来到views.py文件中:
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    class ResizeView(workflows.WorkflowView):
    workflow_class = project_workflows.ResizeInstance #说明resize功能会使用workflow
    success_url = reverse_lazy("horizon:project:instances:index") #执行成功之后要跳转的界面
    #以上在Horizon界面启动时就会加载

    def get_context_data(self, **kwargs): #(4)获取context字典中的数据
    context = super(ResizeView, self).get_context_data(**kwargs) #获取context字典
    context["instance_id"] = self.kwargs['instance_id'] #向context中写入值
    return context #返回context

    @memoized.memoized_method
    def get_object(self, *args, **kwargs): #(1)获取实例的方法
    instance_id = self.kwargs['instance_id'] #获取实例id
    try:
    instance = api.nova.server_get(self.request, instance_id) #根据实例ID来获取实例
    except Exception:
    redirect = reverse("horizon:project:instances:index") #重定向的界面
    msg = _('Unable to retrieve instance details.') #显示信息
    exceptions.handle(self.request, msg, redirect=redirect)
    flavor_id = instance.flavor['id'] #获取当前实例的云主机类型ID
    flavors = self.get_flavors()#获取系统中的云主机类型
    if flavor_id in flavors:
    instance.flavor_name = flavors[flavor_id].name #如果存在云主机类型直接获取类型名字
    else:
    try:
    flavor = api.nova.flavor_get(self.request, flavor_id)
    #不存在云主机类型的话调用api去获取
    instance.flavor_name = flavor.name #获取类型名字
    except Exception:
    msg = _('Unable to retrieve flavor information for instance '
    '"%s".') % instance_id #定义异常信息
    exceptions.handle(self.request, msg, ignore=True)
    instance.flavor_name = _("Not available")
    return instance #最终函数将返回值放到context字典中,context常用在Django中传递字典参数,用来填充网页上的动态数据

    @memoized.memoized_method
    def get_flavors(self, *args, **kwargs): #(2)获取到云主机的类型
    try:
    flavors = api.nova.flavor_list(self.request) #调用api获取到现有的云主机类型
    return SortedDict((str(flavor.id), flavor) for flavor in flavors)
    except Exception:
    redirect = reverse("horizon:project:instances:index") #重定向的界面
    exceptions.handle(self.request,
    _('Unable to retrieve flavors.'), redirect=redirect) #异常处理

    def get_initial(self): #(3)获取初始化数据,为form.py提供数据
    initial = super(ResizeView, self).get_initial()#获得初始化数据
    _object = self.get_object()
    if _object:
    initial.update({'instance_id': self.kwargs['instance_id'], #实例ID
    'name': getattr(_object, 'name', None), #名字
    'old_flavor_id': _object.flavor['id'], #旧的云主机ID
    'old_flavor_name': getattr(_object, 'flavor_name', ''), #旧的云主机类型
    'flavors': self.get_flavors()})
    return initial #返回初始化信息
    下面转到resize_instance.py文件:
    class ResizeInstance(workflows.Workflow):
    #下面几行代码是初始化一些信息
    slug = "resize_instance"
    name = _("Resize Instance")
    finalize_button_name = _("Resize")
    success_message = _('Scheduled resize of instance "%s".') #成功时的提示消息
    failure_message = _('Unable to resize instance "%s".') #失败时的提示消息
    success_url = "horizon:project:instances:index" #成功时要跳转到的URL地址
    default_steps = (SetFlavorChoice, create_instance.SetAdvanced) #调用另外两个类去实现工作流步骤

    def format_status_message(self, message):
    return message % self.context.get('name', 'unknown instance')

    @sensitive_variables('context')
    def handle(self, request, context): #当点击resize按钮之后会触发
    instance_id = context.get('instance_id', None) #实例ID
    flavor = context.get('flavor', None) #云主机类型
    disk_config = context.get('disk_config', None) #从context字典中获取到关于此实例的信息
    try:
    api.nova.server_resize(request, instance_id, flavor, disk_config) #调用api进行resize操作
    return True
    except Exception:
    exceptions.handle(request)
    return False

    所有关于其他组件api的调用都在openstack_dashboard/api包下面:
    找到server_resize方法:
    def server_resize(request, instance_id, flavor, disk_config=None, **kwargs):
    novaclient(request).servers.resize(instance_id, flavor,
    disk_config, **kwargs) #调用api获取

自定义Horizon

运行如下脚本:

1
./run_tests.sh -N -m startpanel mypanel --dashboard=openstack_dashboard.dashboards.admin --target=openstack_dashboard/dashboards/admin/mypanel

生成mypanel目录如下:
mypanel目录
具体源码在此不详细说明,代码编写以及注册菜单之后,生成的菜单以及源码结构如下:
菜单以及源码结构
自定义一个panel,主要用来显示系统当前运行的服务,最终界面如下:
最终界面

文章目录
  1. 1. 分析环境搭建
  2. 2. 基于源码调试
  3. 3. Django框架
  4. 4. Horizon源码结构
  5. 5. 自定义Horizon