OpenStack之Horizon源码分析
分析环境搭建
利用Devstack搭建一个用户开发的单节点OpenStack平台,具体搭建的教程在此不介绍。
基于源码调试
Ubuntu+Eclipse+pydev
Horizon源码被修改之后, 到/opt/stack/horizon目录下执行指令 python manage.py runserver 10.82.82.241:8001从源码启动服务,访问Horizon界面。
Django框架
- Python Web框架,MVT架构
- Models:数据模型
- Views:控制用户所见
- Templates:模板,用户真正所见的
如上图所示,用户通过浏览器发送请求URL,urls.py文件会将不同的URL请求映射到不同的view中,views通过数据模型model请求数据库,在Horizon中,所有对其他组件的操作都是对于API的调用,所以在Horizon中不涉及数据库的访问。Views请求到数据库之后,会传入到Templates中,Templates中存放的是html文件,用来显示最终的界面给用户。
下面是个人开发的基于Django框架的demo,代码结构以及最终界面如下:Horizon源码结构
如上图所示:Dashboard代码分两部分:openstack-dashboard和horizon。openstack-dashboard中存放了系统用到的所有资源文件、针对于当前界面新建的一些实例生成的view,Apache也是映射这部分代码;horizon是系统具体显示的代码实现,定义了一些公共的可重用的显示组件。
如上图所示:Horizon源码结构非常清晰,dashboards下面的每一个包都会对应到界面左侧导航栏的每一个panel group,展开每一个包会发现下面的每一个子包都会对应panel group中的一个panel。
接下来以调整云主机大小为例分析Horizon源码结构,界面以及对应的源码结构如下图:
首先,在url.py中会存在对应的url映射:
接下来到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
86class 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目录如下:
具体源码在此不详细说明,代码编写以及注册菜单之后,生成的菜单以及源码结构如下:
自定义一个panel,主要用来显示系统当前运行的服务,最终界面如下: