进入openstack这个领域大约有一年了,由于某些原因,对openstack各方面都有些了解,不过对openstack也就感觉刚刚入门而已。这期间对openstack的界面进行过一些定制,现在进行个梳理总结。博文中如有说得不对的地方请大家谅解,同时欢迎大家指正,共同提升。
openstack的dashboard是允许用户管理openstack资源和服务的一个web接口。
openstack的界面相关的代码有三部分:
/usr/lib/python2.7/site-packages/horizon
/usr/share/openstack-dashboard
/usr/lib/python2.7/site-packages/openstack_auth
openstack的菜单总共分为三级,Dashboard、Panelgroup、Panel,分别如下图标记1/2/3所示
要增加一个panel首先得简单看看代码结构,进入/usr/share/openstack-dashbaord/openstack_dashboard/dashboards目录,会看到如下的结构:
|--__init__.py
|--admin
|--project
|--identity
|--router
|--settings
以上几个文件夹就代表了openstack界面上的几个一级菜单(Dashboard),分别是admin(管理员)、project(项目)、identity(Identity)、settings(设置,这个一级菜单需要点击右上角下拉中的设置才会显示出来)、router(配置文件中将profile_support打开可见,ciso nexus 1000v的管理面板)。现在选择admin进去看看它的目录结构。
admin
admin
|--__init__.py
|--aggregates
|--dashboard.py
|--defaults
|--flavors
|--hypervisors
|--images
|--info
|--instances
|--metadta_defs
|--metering
|--models.py
|--networks
|--overview
|--routers
|--volumes
仔细研究可以发现这些文件夹都一一对应了管理员(admin)下的各个Panel,首先看看Dashboard的类似于配置文件的东西dashboard.py,它就是描述Dashboard的python文件。
|-dashboard.py
from django.utils.translation import ugettext_lazy as _
import horizon
class SystemPanels(horizon.PanelGroup):
slug = "admin"
name = _("System")
panels = ('overview', 'metering', 'hypervisors', 'aggregates',
'instances', 'volumes', 'flavors', 'images',
'networks', 'routers', 'defaults', 'metadata_defs', 'info')
class Admin(horizon.Dashboard):
name = _("Admin")
slug = "admin"
panels = (SystemPanels,)
default_panel = 'overview'
permissions = ('openstack.roles.admin',)
horizon.register(Admin)
浏览一遍该文件可以看到代码结构还是非常清晰的。其中class Admin即是描述管理员Dashboard的类,它继承了horizon(/usr/lib/python2.7/site-packages/horizon)中的Dashboard基类(描述一级菜单的基类),class Admin中有几个属性,分别为name、slug、panels、default_panel、permissions,根据基类提供的信息可以知道它们分别代表的是名称、id、该Dashboard下的panels、默认panel、权限。除此之外,还有一个class SystemPanels,可以看到它是继承horizon中的PanelGroup,根据openstack三级菜单结构可以知道它应该就是描述的二级菜单了,class SystemPanels有slug、name、panels三个属性,分别代表的是id、名称、属于它的panel。最后有一个horizon.register就是注册Dashboard了。现在看来,如果要增加一个panel到Admin这个菜单下面,无非需要做两步:
接下来就要看看代表panel的文件夹下有什么东西了。进入instances(实例)文件夹,目录结构如下:
|--instances
|--forms.py
|--__init__.py
|--panel.py
|--tables.py
|--templates
|--tests.py
|--urls.py
|--views.py
该文件夹下的东西就是描述Panel instances(实例)的相关文件了。看这文件结构应该就是一个django app了。分别看看各个文件:
forms.py是表单,描述的是弹出框之类的,现在主要目的是增加一个panel,暂且不看。
panel.py应该就是描述这个panel基本信息的文件了,这个必须看看
from django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.dashboards.admin import dashboard
class Instances(horizon.Panel):
name = _("Instances")
slug = 'instances'
permissions = ('openstack.roles.admin', 'openstack.services.compute')
dashboard.Admin.register(Instances)
可以看到以上代码结构与dashboard.py是非常类似的。这里class Instances继承了horzion中的Panel这个基类,很明显这就是描述panel的基类了。这Instances现在有三个属性,分别是name(名称)、slug(id)、permissions(权限)。注意最后一行代码,这行代码是import的前面我所说到的dashboard.py。最后一行也就是说Admin的Dashboard类用register方法注册了该panel,层级关系非常清晰。
tables.py顾名思义是描述表格之类的,看看它的代码,(这里省略了部分代码)
...
class AdminInstancesTable(tables.DataTable):
TASK_STATUS_CHOICES = (
(None, True),
("none", True)
)
STATUS_CHOICES = (
("active", True),
("shutoff", True),
("suspended", True),
("paused", True),
("error", False),
("rescue", True),
("shelved", True),
("shelved_offloaded", True),
)
tenant = tables.Column("tenant_name", verbose_name=_("Project"))
# NOTE(gabriel): Commenting out the user column because all we have
# is an ID, and correlating that at production scale using our current
# techniques isn't practical. It can be added back in when we have names
# returned in a practical manner by the API.
# user = tables.Column("user_id", verbose_name=_("User"))
host = tables.Column("OS-EXT-SRV-ATTR:host",
verbose_name=_("Host"),
classes=('nowrap-col',))
name = tables.Column("name",
link="horizon:admin:instances:detail",
verbose_name=_("Name"))
image_name = tables.Column("image_name",
verbose_name=_("Image Name"))
ip = tables.Column(project_tables.get_ips,
verbose_name=_("IP Address"),
attrs={'data-type': "ip"})
size = tables.Column(project_tables.get_size,
verbose_name=_("Size"),
attrs={'data-type': 'size'})
status = tables.Column(
"status",
filters=(title, filters.replace_underscores),
verbose_name=_("Status"),
status=True,
status_choices=STATUS_CHOICES,
display_choices=project_tables.STATUS_DISPLAY_CHOICES)
task = tables.Column("OS-EXT-STS:task_state",
verbose_name=_("Task"),
empty_value=project_tables.TASK_DISPLAY_NONE,
status=True,
status_choices=TASK_STATUS_CHOICES,
display_choices=project_tables.TASK_DISPLAY_CHOICES)
state = tables.Column(project_tables.get_power_state,
filters=(title, filters.replace_underscores),
verbose_name=_("Power State"),
display_choices=project_tables.POWER_DISPLAY_CHOICES)
created = tables.Column("created",
verbose_name=_("Time since created"),
filters=(filters.parse_isotime,
filters.timesince_sortable),
attrs={'data-type': 'timesince'})
class Meta(object):
name = "instances"
verbose_name = _("Instances")
status_columns = ["status", "task"]
table_actions = (project_tables.TerminateInstance,
AdminInstanceFilterAction)
row_class = AdminUpdateRow
row_actions = (project_tables.ConfirmResize,
project_tables.RevertResize,
AdminEditInstance,
project_tables.ConsoleLink,
project_tables.LogLink,
project_tables.CreateSnapshot,
project_tables.TogglePause,
project_tables.ToggleSuspend,
MigrateInstance,
LiveMigrateInstance,
project_tables.SoftRebootInstance,
project_tables.RebootInstance,
project_tables.TerminateInstance)
因为该文件是描述表格的,在这省略了部分代码,只看关键描述表格的这部分。class AdminInstancesTable继承的是DataTable这个基类,看看代码的结构可以很容易看出tables.Column就是描述表格的每一列的,其中class Meta顾名思义是描述该表格的元数据的类。它有表格的名字、动作、表动作等属性。
templates是静态模板,先只看看index.html
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Instances" %}{% endblock %}
{% block main %}
{{ table.render }}
{% endblock %}
该模板继承了base.html,然后一个标题块block title、一个主要部分block main,table.render是传过来的值,它就是代表的表格那一块了。
tests.py为测试代码;
urls.py是决定了界面的url;
from django.conf.urls import patterns
from django.conf.urls import url
from openstack_dashboard.dashboards.admin.instances import views
INSTANCES = r'^(?P[^/]+)/%s$'
urlpatterns = patterns(
'openstack_dashboard.dashboards.admin.instances.views',
url(r'^$', views.AdminIndexView.as_view(), name='index'),
url(INSTANCES % 'update', views.AdminUpdateView.as_view(), name='update'),
url(INSTANCES % 'detail', views.DetailView.as_view(), name='detail'),
url(INSTANCES % 'console', 'console', name='console'),
url(INSTANCES % 'vnc', 'vnc', name='vnc'),
url(INSTANCES % 'spice', 'spice', name='spice'),
url(INSTANCES % 'rdp', 'rdp', name='rdp'),
url(INSTANCES % 'live_migrate', views.LiveMigrateView.as_view(),
name='live_migrate'),
)
该文件每个url对应了views的一个函数或者类,index是描述主页的,其它url是相关功能的url。
views.py处理用户请求,从urls.py中反应过来,获取数据。
...
class AdminIndexView(tables.DataTableView):
table_class = project_tables.AdminInstancesTable
template_name = 'admin/instances/index.html'
page_title = _("Instances")
def has_more_data(self, table):
return self._more
def get_data(self):
...
省略了views.py的部分代码,以后的博文中会对其它的进行描述,现在只是增加panel,只看看描述index页面的类。class AdminIndexView继承的是DataTableView这个类,这个类有table_class、template_name等几个属性,可以比较明显的看出它应该就是为那个表格类服务的了,主要功能就是获取数据,设置页面title、指定静态模板了。
那么现在就可以模仿以上这个结构自行增加一个panel了,构造一个类似的文件夹:
mypanel
|--__init__.py
|--panel.py
|--tables.py
|--templates
|--mypanel
|--index.html
|--urls.py
|--views.py
它们代码如下:
import horizon
from openstack_dashboard.dashboards.admin import dashboard
class Mypanel(horizon.Panel):
name = "mypanel"
slug = 'mypanel'
permissions = ('openstack.roles.admin', 'openstack.services.compute')
dashboard.Admin.register(Mypanel)
from horizon import tables
class MypanelTable(tables.DataTable):
column1 = tables.Column("column1", verbose_name="column1")
class Meta(object):
name = "mypaneltable"
verbose_name = "mypaneltable"
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "mypanel" %}{% endblock %}
{% block main %}
{{ table.render }}
{% endblock %}
from django.conf.urls import patterns
from django.conf.urls import url
from openstack_dashboard.dashboards.admin.mypanel import views
urlpatterns = patterns(
'openstack_dashboard.dashboards.admin.mypanel.views',
url(r'^$', views.MypanelIndexView.as_view(), name='index'),
)
from horizon import tables
from openstack_dashboard.dashboards.admin.mypanel \
import tables as project_tables
class MypanelIndexView(tables.DataTableView):
table_class = project_tables.MypanelTable
template_name = 'admin/mypanel/index.html'
page_title = "mypanel"
def get_data(self):
data = []
return data
除此之外,在admin中的dashboard.py加上这个panel的slug。
- dashboard.py
from django.utils.translation import ugettext_lazy as _
import horizon
class SystemPanels(horizon.PanelGroup):
slug = "admin"
name = _("System")
panels = ('overview', 'metering', 'hypervisors', 'aggregates',
'instances', 'volumes', 'flavors', 'images',
'networks', 'routers', 'defaults', 'metadata_defs', 'info','mypanel') #加到属性panels下
class Admin(horizon.Dashboard):
name = _("Admin")
slug = "admin"
panels = (SystemPanels,)
default_panel = 'overview'
permissions = ('openstack.roles.admin',)
horizon.register(Admin)
然后重启httpd服务,查看页面
更多精彩文章,请搜索微信公众号“扶艾”。我们定期分享OpenStack相关技术文章,在这里,只有纯干货。