分享

openstack ice自定义调度算法项目详解(horizon、novaclient、api、scheduler、db)

xioaxu790 发表于 2014-10-31 21:16:21 [显示全部楼层] 只看大图 回帖奖励 阅读模式 关闭右栏 3 20825
问题导读
1、你如何理解从url拼装到nova后端请求数据?
2、获取当前租户所有可用hosts的API是如何实现的?
3、如何调用api添加到底层数据库做持久化?





第一部分:页面层即horizon与novaclient

1、首先上图此功能:
图1-1
1.png


右边的admin与service为租户信息。点击modify isolation 对该租户计算资源进行隔离配置

图1-2
2.png



模态框有三个tab标签页,分别为控制host、aggregate、zone。以host为例,右边为该租户生成虚拟机能够选择的计算节点。左边则为被隔离节点。

2、讲解图1-1:即主页,index。
图2-1:isolations模块文件结构
申明:每次截图pycharm时候都会截大图,目的是让大家能够通过编辑器最上面标签看到文件的路径,关于这点后续博文中则不再提及。
3.png




图2-2:TenantsTable定义了图1-1表格,以及按钮
1.png


其中row_actions = (ViewMembersLink,)表示为下图按钮:
2.png


3、跟进到ViewMembersLink:
3.png



4、根据url = "horizon:admin:isolations:update"跟进到urls.py文件:
1.png



5、跟进到UpdateIsolationView:
2.png


其中self.kwargs["tenant_id"]是获取url中的tenant_id。

6、跟进到UpdateIsolation:
3.png


其中handle则是点击页面保存之后执行。此处代码将在后面步骤9中进行讲解。
  1. default_steps = (UpdateZoneMembers, UpdateAggregateMembers,UpdateHostMembers,)
复制代码


上述定义的三个步骤即为图1-2中的三个tab标签。接下来以UpdateHostMembers为例进行讲解。

7、跟进到UpdateHostMembers
1.png


8、跟进到UpdateHostMembersAction:
2.png

此处主要是将动态多选插件数据初始化如图1-2,下面附上代码:
  1. class UpdateHostMembersAction(workflows.MembershipAction):
  2.     def __init__(self, request, *args, **kwargs):
  3.         super(UpdateHostMembersAction, self).__init__(request, *args, **kwargs)
  4.         err_msg = _('Unable to retrieve user list. Please try again later.') #定义错误提示信息
  5. #下面5行代码可以直接使用,复制粘贴即可
  6.         default_role_field_name = self.get_default_role_field_name()
  7.         self.fields[default_role_field_name] = forms.CharField(required=False)
  8.         self.fields[default_role_field_name].initial = 'member' #此处定义了一个member名字供步骤7中contribute获取数据
  9.         field_name = self.get_member_field_name('member')
  10.         self.fields[field_name] = forms.MultipleChoiceField(required=False)
  11.         tenant_id = self.initial['tenant_id'] #此处获取url中的tenant_id
  12.         isolated_scheduler_hosts = []
  13.         try:
  14. #通过api从数据库获取当前租户所有可用的hosts(即图1-2的右边选择框)
  15.             isolated_scheduler_hosts = api.nova.get_isolated_scheduler_list(request, tenant_id, type="host")
  16.         except Exception:
  17.             exceptions.handle(request, err_msg)
  18.         enabled_host_names = [(host.isolated_name) for host in isolated_scheduler_hosts]
  19.         hosts = []
  20.         try: #此处获取所有可供选择的hosts,即图1-2中的左右之和
  21.             hosts = api.nova.host_list(request)
  22.         except Exception:
  23.             exceptions.handle(request, err_msg)
  24.         host_names = []
  25.         for host in hosts:
  26.             if host.host_name not in host_names and host.service == u'compute':
  27.                 host_names.append(host.host_name)
  28.         host_names.sort()
  29.         all_names = [(host_name, host_name) for host_name in host_names]
  30.         #if sql table isolated_scheduler don't have any record, then all host is available
  31.         if len(enabled_host_names) == 0:
  32.             enabled_host_names = [(host_name) for host_name in host_names]
  33.         self.fields[field_name].choices = all_names #此处将图1-2中左右之和复制给choices
  34.         self.fields[field_name].initial = enabled_host_names #此处将图1-2中右边已选择框中的值赋值给initial
  35. #通过上述两个赋值操作,则框架自动会将数据渲染到页面
  36.     class Meta:
  37.         name = _("UpdateHostMembers") #标签页的名字定义
  38.         slug = "UpdateHostMembers"
复制代码



注意:上图中的all_names ,和enabled_host_names 数据结构举例如下:(其中initial 的初值只能有名字。)
  1.         all_names = [(u'mynode158', u'mynode158'), (u'node10_31', u'node10_31'),(u'mynode1581', u'mynode1581'), (u'node10_311', u'node10_311'),(u'mynode1581', u'mynode1581'), (u'node10_311', u'node10_311')]
  2.         enabled_host_names = [(u'mynode158'), (u'node10_31')]
  3.       
  4.         self.fields[field_name].choices = all_names
  5.         self.fields[field_name].initial = enabled_host_names
复制代码


其中initial 的初值只能有名字。all_names 数据结构为 [(u'mynode158', u'mynode158')] 而enabled_host_names 数据结构为 [(u'mynode158')],数据结构错误则渲染不出来

9、讲解UpdateIsolation代码(页面点击保存按钮则会执行handle函数进行业务逻辑处理,重点讲解handle函数):
  1. class UpdateIsolation(workflows.Workflow):
  2.     slug = "update_isolation"
  3.     name = _("Edit Isolation")
  4.     finalize_button_name = _("Save")
  5.     success_message = _('Modified isolation "%s".')
  6.     failure_message = _('Unable to modify isolation "%s".')
  7.     success_url = "horizon:admin:isolations:index"
  8.     default_steps = (UpdateZoneMembers, UpdateAggregateMembers,UpdateHostMembers,)
  9.     def format_status_message(self, message):
  10.         return message % self.context.get('name', 'unknown isolation')
  11.     def handle(self, request, data):
  12.    
  13.         tenant_id = data['tenant_id'] #获取tenant_id
  14.         ################################################################################################
  15.         #此处获取图1-2中右侧选择框中的数据。此处数据的初始化在步骤7中的contribute
  16.         selected_host_names = self.context['enabled_host_names']
  17.         enabled_host_names = []
  18.         try:
  19.             # 此处获取隔离调度数据库表中已有的hosts
  20.             enabled_hosts = api.nova.get_isolated_scheduler_list(request, tenant_id, type="host")
  21.             enabled_host_names = [(host.isolated_name) for host in enabled_hosts]
  22.         except Exception:
  23.             exceptions.handle(request, "get hosts exception")
  24.         try:
  25.             all_host_names = list(set(selected_host_names + enabled_host_names))
  26. #重点:此处将前端页面图1-2右侧选择的,与底层数据库实际存在的做操作,即得到本次操作减少的或者增加的hsots,从而获取进行处理
  27.             host_add_list = list(set(all_host_names) - set(enabled_host_names))
  28.             host_remove_list = list(set(all_host_names) - set(selected_host_names))
  29.             tenant = api.keystone.tenant_get(request, tenant_id)
  30.             tenant_name = tenant.name
  31.             for host_name in host_add_list:#将上述得到新增host的列表,调用添加api添加到底层数据库持久化,此api更多跟进参考步骤10
  32.                 api.nova.isolatation_add_host(request, host_name, tenant_id, tenant_name)
  33.             for host_name in host_remove_list:#将上述得到减少host的列表,调用添加api添加到底层数据库持久化
  34.                 api.nova.isolatation_remove_host(request, host_name, tenant_id, tenant_name)
  35.         except Exception:
  36.             exceptions.handle(request, "modify the hosts with tenant exception")
复制代码
  1. #以下为处理aggregate与zones原理与host处理一致,不再重复讲解
  2.         #################################################################################################
  3.         selected_aggregate_names = self.context['enabled_aggregate_names']
  4.         enabled_aggregate_names = []
  5.         try:
  6.             enabled_aggregates = api.nova.get_isolated_scheduler_list(request, tenant_id, type="aggregate")
  7.             enabled_aggregate_names = [(aggregate.isolated_name) for aggregate in enabled_aggregates]
  8.         except Exception:
  9.             exceptions.handle(request, "get aggregates exception")
  10.         try:
  11.             all_aggregate_names = list(set(selected_aggregate_names + enabled_aggregate_names))
  12.             aggregate_add_list = list(set(all_aggregate_names) - set(enabled_aggregate_names))
  13.             aggregate_remove_list = list(set(all_aggregate_names) - set(selected_aggregate_names))
  14.             tenant = api.keystone.tenant_get(request, tenant_id)
  15.             tenant_name = tenant.name
  16.             for aggregate_name in aggregate_add_list:
  17.                 api.nova.isolatation_add_aggregate(request, aggregate_name, tenant_id, tenant_name)
  18.             for aggregate_name in aggregate_remove_list:
  19.                 api.nova.isolatation_remove_aggregate(request, aggregate_name, tenant_id, tenant_name)
  20.         except Exception:
  21.             exceptions.handle(request, "modify the aggregates with tenant exception")
  22.         #################################################################################################
  23.         selected_zone_names = self.context['enabled_zone_names']
  24.         enabled_zone_names = []
  25.         try:
  26.             enabled_zones = api.nova.get_isolated_scheduler_list(request, tenant_id, type="zone")
  27.             enabled_zone_names = [(zone.isolated_name) for zone in enabled_zones]
  28.         except Exception:
  29.             exceptions.handle(request, "get zones exception")
  30.         try:
  31.             all_zone_names = list(set(selected_zone_names + enabled_zone_names))
  32.             zone_add_list = list(set(all_zone_names) - set(enabled_zone_names))
  33.             zone_remove_list = list(set(all_zone_names) - set(selected_zone_names))
  34.             tenant = api.keystone.tenant_get(request, tenant_id)
  35.             tenant_name = tenant.name
  36.             for zone_name in zone_add_list:
  37.                 api.nova.isolatation_add_zone(request, zone_name, tenant_id, tenant_name)
  38.             for zone_name in zone_remove_list:
  39.                 api.nova.isolatation_remove_zone(request, zone_name, tenant_id, tenant_name)
  40.         except Exception:
  41.             exceptions.handle(request, "modify the zones with tenant exception")
  42.         return True
复制代码




10,继续跟进步骤9中天骄host到数据库的isolatation_add_host API:
1.png


clipboard.png




此处最后会拼装url到nova后端请求数据。

到此处从horizon到novaclient全部讲解完毕。


第二部分:wsgi发布及底层数据库操作:

1、底层wsgi发布:
1.png


重点关注:
  1. member_actions={"action": "POST","list_hosts_by_tenant_id":"GET",
  2.                                 "get_isolated_scheduler_zone_list":"GET",
  3.                                 "get_isolated_scheduler_aggregate_list":"GET",
  4.                                 "get_isolated_scheduler_host_list":"GET"})
复制代码



这样就添加了各种自定义的api路由,前端curl就能够请求到此处底层nova api。
接下来以add_host为例讲解,其他api类似。
首先查看路由是否发布成功:
2.png



当前端发送如下URL请求时候:
  1. curl -i 'http://192.168.10.31:8774/v2/2a4fe5e733e44982b1d576c5a0fe4bfd/os-isolation-hosts/2a4fe5e733e44982b1d576c5a0fe4bfd/action' -X POST -H "User-Agent: python-novaclient" -H "Content-Type: application/json" -H "Accept: application/json" -H "X-Auth-Token: $TOKEN" -d '{"add_host": {"isolated_name": "node31","tenant_name":"admin"}}'
复制代码


根据上述路由规则,对应到图1-1(此处图1-1指的是第二部分图1-1)中action : POST
1.png



2、根据wsgi发布定义的IsolationController(),找到IsolationController类中对应的action函数:
1.png



2.png


在根据url中的参数add_host,调用self._add_host,:
3.png



3、跟进到self._add_host:
1.png

其中@get_isolatedname_and_username_from_body注解是一个装饰器,用来解析参数:
  1. def get_isolatedname_and_username_from_body(fn):
  2.     """Makes sure that the host exists."""
  3.     def wrapped(self, req, id, body, *args, **kwargs):
  4.         if len(body) == 2 and "isolated_name" in body and "tenant_name" in body:
  5.             isolated_name = body['isolated_name']
  6.             tenant_name = body['tenant_name']
  7.         else:
  8.             raise exc.HTTPBadRequest()
  9.         return fn(self, req, id, isolated_name, tenant_name , *args, **kwargs)
  10.     return wrapped
复制代码


4、跟进到add_host_to_isolation:
1.png



5、跟进到isolation_obj.IsolationList.add_host_to_isolation:
2.png


此处开始即调用db模块,和数据库进行交互了。

6、跟进到db.add_host_to_isolation:
3.png



7、继续跟进到IMPL.add_host_to_isolation:
1.png



此处为最后操作数据代码,附上代码详解:
  1. # add by ttx 2014-9-17
  2. @require_context
  3. def add_host_to_isolation(context, tenant_id, tenant_name, isolated_name, isolated_id):
  4. #此处代码判断是否在数据库中已经存在该条数据,只是被soft_delete掉(即图7-1中所示),此处代码参考步骤8
  5.     result = get_isolation_query(context, tenant_id, isolated_name, 0)
  6.     if result: #如果已经存在被软删除过的数据,则将deleted更新为0即可
  7.         values = {
  8.                     'updated_at': timeutils.datetime.datetime.utcnow(),
  9.                     'deleted_at' : None,
  10.                     'deleted' : 0}
  11.         result.update(values) #构造一个更新数据字典
  12.         result.save() #更新数据
  13.     else: #假若数据库没有任何记录,则直接新增
  14.         session = get_session()
  15.         values = {'tenant_id': tenant_id,
  16.                   'tenant_name': tenant_name,
  17.                   'project_id': context.project_id,
  18.                   'isolated_type': '0',
  19.                   'isolated_name': isolated_name,
  20.                   'isolated_id': isolated_id,
  21.                   'created_at':timeutils.datetime.datetime.utcnow()}
  22.         isolation_ref = models.IsolatedSchedule()
  23.         isolation_ref.update(values)
  24.         session.add(isolation_ref)
  25.         session.flush()#新增完之后需要执行flush才能立即写入到数据库
复制代码



图7-1:(即node31的deleted不为0,表示被软删除过)
1.png



8、get_isolation_query函数讲解(此处其实就是在数据库进行一个where查询,返回符合where字句的第一条数据):

  1. #isolated_type 0==host 1==aggregate 2=zone
复制代码

到此处从nova底层操作资源隔离数据库表API全部讲解完毕。


第三部分:自定义添加数据库表:
未完待续

第四部分:自定义资源隔离调度算法详解:

附上调度算法:
1.png

  1. # add by ttx 2014-9-9
  2. from oslo.config import cfg
  3. from nova import db
  4. from nova.scheduler import filters
  5. class IsolatedscheduleFilter(filters.BaseHostFilter):
  6.     """Keep specified instances to selected zone or aggregate or host."""
  7.     # we can make allHosts with a static variable,
  8.     # with this way it doesn't need get data every time.
  9. #此函数将会在每次调度过滤判断自动调用,更多参考nova-scheduler详解 openstack-ice版
  10.     def host_passes(self, host_state, filter_properties):
  11.         context = filter_properties['context'].elevated()
  12. #从数据库获取当前租户所有可以使用的zones
  13.         scheduler_zone_infos = db.scheduler_info_get_by_tenant_id(context, context.project_id, type="zone")
  14.         zone_hosts = []
  15. #根据可用的zones迭代获取隶属于该zones下面所有的hosts
  16.         for info in scheduler_zone_infos:
  17.             ag_list = db.hosts_get_by_zone(context, info.isolated_id, key='availability_zone')
  18.             for ag in ag_list:
  19.                 zone_hosts = zone_hosts + ag.hosts
  20. #从数据库获取当前租户所有可以使用的aggregates
  21.         scheduler_aggregate_infos = db.scheduler_info_get_by_tenant_id(context, context.project_id, type="aggregate")
  22.         aggregate_hosts = []
  23. #根据可用的aggregates迭代获取隶属于该aggregates下面所有的hosts
  24.         for info in scheduler_aggregate_infos:
  25.             hosts = db.aggregate_host_get_all(context, info.isolated_id)
  26.             aggregate_hosts = aggregate_hosts + hosts
  27. #从数据库获取当前租户所有可以使用的hosts
  28.         scheduler_host_infos = db.scheduler_info_get_by_tenant_id(context, context.project_id, type="host")
  29.         just_hosts = []
  30.         for info in scheduler_host_infos:
  31.             just_hosts.append(info.isolated_name)
  32. #将所有可以的zones下面的hosts和aggregates下面的hosts以及可用的hosts合并,即为该租户所有可用的hosts
  33.         allHosts = list(set(zone_hosts + aggregate_hosts + just_hosts))
  34.         #if host in allHosts or allHosts is empty return True
  35. #如果当前host在上述allHosts ,则返回true,上层将会yield加入到可用hosts集合中
  36. #关于yield用法参考博文:Python yield语法 使用实战详解
  37.         if host_state.host in allHosts or len(allHosts) == 0: #如果数据库没有任何信息,即初始状态则所有hosts都可用
  38.             return True
  39.         return False
  40.     # this function not use now
  41.     def get_enabled_hosts(self, hosts, filter_properties):
  42.         pass
复制代码


到此整个资源隔离调度算法从最上层页面操作到数据库最底层,以及数据库设计,调度算法过滤都讲解完毕。

已有(3)人评论

跳转到指定楼层
韩克拉玛寒 发表于 2014-11-1 09:05:33
谢谢楼主,辛苦了。
回复

使用道具 举报

oscar 发表于 2014-11-8 23:06:40
Your dashboard is beautiful!  Where do you work?
回复

使用道具 举报

cerberus 发表于 2014-11-10 16:32:34
写的很详细,谢谢楼主了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条