OpenStack对接ODL控制器之ODL-Networking模块详解

OpenStack支持多种SDN控制器的对接,之前的对接方案是将一个mechanism driver的一个python文件放在neutron ml2的目录下面,使得OpenStack对neutron的操作可以转发给SDN控制器。但是这样的处理方式存在许多问题,后来的driver被命名为xxx-networking的项目,主要是为了更好地对接HA部署的OpenStack,因为Neutron server分布在多个物理机上的时候会出现更加复杂的情况,而导致SDN控制器接受到错误的消息,这篇文章以ODL-Networking为例,介绍ODL是如何实现的,自研控制器的对接也可以参考ODL的设计思想。

ODL的ODL-Networking项目位于下面的链接中
https://github.com/openstack/networking-odl

ODL-Networking 介绍

OpenStack networking-odl是一个二进制文件的一个plugin,它的作用是集成OpenStack的Neutron API和OpenDaylight SDN控制器。networking-odl有ML2 driver和L3 plugin的模块,可以支持OpenStack Neutron L2和L3的API,再转发数据到OpenDaylight控制器上

ODL驱动架构v1

ODL-Networking分为v1 driver和v2 driver v1 driver会把OpenStack上的网络的更改同步到ODL控制器上。举个例子来说,用户创建了一个网络,首先这个操作会被写进OpenStack的数据库中,然后ODL driver会发送一个POST请求到ODL控制器

虽然这个操作看起来很简单,但是却有一些隐含的问题

  • ODL并不是实时反应的,即使REST call成功给到了ODL,ODL也不一定可以马上响应
  • 在负载很大的时候(可能是odl或者openstack),两边的同步可能会存在瓶颈
  • 在遇到odl与openstack同步失败的时候,v1 driver在下次的call REST API的时候会尝试去“full sync”整个neutron DB,所以会导致下次的REST API call花费很长的时间
  • 在多个rest api call竞争的情况下可能会出问题:
    1.例如,创建subnet的时候再创建port的情况,再neutron的HA部署的时候,这2个创建的消息会同步的通过v1 driver发给ODL,导致创建port失败
    2.在前面的“full sync”的操作的时候,这个时候数据库如果正在删除资源,那么这个时候同步的话,会把下一时刻会被删除的资源,同步到odl这边,形成openstack和odl的资源不同步

ODL驱动架构v2

面对前面的这些问题,社区又重新设计了v2 driver

  • v2 driver最重要的机制是引入了journaling(记录)机制,v2 driver会把openstack传给open daylight的数据记录在一个journal表中,journal表使用一堆队列来构成的。而不是直接把openstack的数据传递给odl
  • v2 driver中会起一个journal的线程,它会周期性地检查journal表中是否有新的数据,然后处理新的数据,另外的,openstack neutron的postcommmit的操作也会触发这个线程工作
  • 用户在创建一个网络资源的时候,这条数据会一开始存在neutron DB中,v2 driver再把这条数据存在“journal entry”里面,这个时候触发journal线程来处理这条数据
  • 数据在pre-commit的时候就已经被记录在journal entry中了,以防止这个commit失败的时候,journal entry也可以马上中止,实现消息的同步

Journal Entry架构

  • journal entry一开始创建的时候,把状态置为“pending”等待状态,在这种状态下,entry在等待线程来处理,很多线程在处理entry的时候,只有一个线程可以“选中”这个entry
  • 当这个entry被选中时,状态会被置为“processing”状态,并且被lock住,防止其他的线程来处理这个entry
  • 当线程开始处理的时候,它先检查这个entry的依赖关系,如果这个entry依赖其他的entry,那么这个线程会把这个entry放回原位,然后再处理下一个entry
  • 当这个entry没有其他依赖关系的时候,这个线程会吧entry里面的消息传递给opendaylight,具体是PUT,POST,DELETE这些操作,当这个REST call成功的时候,那么把这个entry的状态置为“completed”
  • 当出现错误的时候,线程首先判断这个是“expected”意料之中的错误(如网络连通性等问题)还是“unexpected”意料之外的错误。对于那些意料之外的错误,会引入一个计数器,并对这个计数器设置一个数字,这个entry会被再次处理,处理的次数是这个计数器的数字。对于那些意料之中的错误,不会改变计数器的数字,当entry再次处理,直到计数器数字的次数后,这个entry就被设置为“failed”状态。否则,这个entry重新被标记为“pending”等待,等待下次被处理。

Entry依赖检查机制

以下是社区的Entry依赖检查机制BP,Entry依赖检查机制将会被提前到entry创建的时候进行
https://blueprints.launchpad.net/networking-odl/+spec/dep-validations-on-create
当前v2 driver的Entry依赖检查机制发生在entry被处理的时候,但是将会被改为entry被创建的时候就进行依赖检查机制 当原先Entry依赖检查机制发生在entry被处理的时候,如果依赖检查机制fail,那么这个entry就会被送回队列,这样的处理方式可能会有以下几个问题

  • entry依赖检查机制fail但是不知道哪里出了问题
  • 很难去debug
  • 当循环依赖的情况发生的话很难定位问题
  • 一个entry可能会被反复进行依赖检查,导致CPU浪费
  • 过多地检查依赖关系的话会导致数据库压力剧增

因此,Entry依赖检查机制将会被提前到entry创建的时候就进行,在entry被创建的时候,journal就会检查这个entry是否有依赖其他的entry,如果有的话,那么把这个entry放在一个链表中。这样的话,journal在选择entry的时候就会只去选择没有其他依赖的entry,当entry被处理后,无论是成功还是失败,依赖这个entry的链表都会被清空,这样可以使得后面的entry不会依赖到它。
下面是journal依赖的表,一个entry如果有依赖的话,那么下面的表中的行列就是这个entry的外键,entry被删除的时候,这些外键也需要被同时删除。entry在被处理的时候,这个entry所依赖的parent被存入parent_id中,依赖这个entry的被存入dependent_id中,在journal处理这个entry的时候,必须要保证这个entry没有与parent有依赖关系(当parent被处理完之后会自动断开所有依赖),这样的话就保证了不会存在parent没处理完,而直接去处理这个entry的情况。

1
2
3
4
5
6
+------------------------+
| odl_journal_dependency |
+------------------------+
| parent_id              |
| dependent_id           |
+------------------------+

Entry恢复机制

以下是社区的Entry恢复机制BP,
https://docs.openstack.org/developer/networking-odl/specs/completed/ocata/journal-recovery.html
Journal entry在处理失败的情况,entry需要再被处理
entry可能由于以下几个原因处理失败

  • 在最大尝试次数条件下,一直处理失败
  • ODL中的数据和OpenStack Neutron数据库不一致
  • Neutron和ODL中的数据同步失败 目前失败的entry就一直会存在journal表中,这样会影响journal表的性能,大量的失败的entry存在journal表中的话对性能的影响还是很大的

现在需要解决的问题是如何处理entry中标记为fail的entry
下面是社区BP的流程图

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
+-----------------+
| For each entry  |
| in failed state |
+-------+---------+
        |
+-------v--------+
| Query resource |
| on ODL (REST)  |
+-----+-----+----+
      |     |                          +-----------+
   Resource |                          | Determine |
   exists   +--Resource doesn't exist--> operation |
      |                                | type      |
+-----v-----+                          +-----+-----+
| Determine |                                |
| operation |                                |
| type      |                                |
+-----+-----+                                |
      |              +------------+          |
      +--Create------> Mark entry <--Delete--+
      |              | completed  |          |
      |              +----------^-+       Create/
      |                         |         Update
      |                         |            |
      |          +------------+ |      +-----v-----+
      +--Delete--> Mark entry | |      | Determine |
      |          | pending    | |      | parent    |
      |          +---------^--+ |      | relation  |
      |                    |    |      +-----+-----+
+-----v------+             |    |            |
| Compare to +--Different--+    |            |
| resource   |                  |            |
| in DB      +--Same------------+            |
+------------+                               |
                                             |
+-------------------+                        |
| Create entry for  <-----Has no parent------+
| resource creation |                        |
+--------^----------+                  Has a parent
         |                                   |
         |                         +---------v-----+
         +------Parent exists------+ Query parent  |
                                   | on ODL (REST) |
                                   +---------+-----+
+------------------+                         |
| Create entry for <---Parent doesn't exist--+
| parent creation  |
+------------------+

entry中标记为fail状态的entry不能对后面进来的entry产生影响
BP的实现可以分为两个部分,第一个部分,当我们检查到entry处于fail状态的时候,这个entry的动作可能是create或者update操作,如果这个资源再ODL中并没有存在、生效的话,那么我们就创建一个新的”create resource”这样的entry出来 其中,处理这个fail的entry是由一个专门的线程来处理,称为”maintenance thread”

代码分析

v2的driver相比以前只有一个python文件来说,代码量大了许多,主要内容在networking_odl目录下
其中bgpvpn,fwaas,lbaas,qos,sfc这几个高级功能都是针对ODL的北向接口开发的,因此参考意义不大。主要的内容还是journal机制的实现以及l2和l3的实现

1
2
3
4
5
6
7
L2_RESOURCES = {ODL_SG: ODL_SGS,
                ODL_SG_RULE: ODL_SG_RULES,
                ODL_NETWORK: ODL_NETWORKS,
                ODL_SUBNET: ODL_SUBNETS,
                ODL_PORT: ODL_PORTS}
L3_RESOURCES = {ODL_ROUTER: ODL_ROUTERS,
                ODL_FLOATINGIP: ODL_FLOATINGIPS}

在判断pending还是processing的时候,通过session.query去数据库中查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def check_for_pending_or_processing_ops(session, object_uuid, seqnum=None,
                                        operation=None):
    q = session.query(models.OpendaylightJournal).filter(
        or_(models.OpendaylightJournal.state == odl_const.PENDING,
            models.OpendaylightJournal.state == odl_const.PROCESSING),
        models.OpendaylightJournal.object_uuid == object_uuid)

    if seqnum is not None:
        q = q.filter(models.OpendaylightJournal.seqnum < seqnum)

    if operation:
        if isinstance(operation, (list, tuple)):
            q = q.filter(models.OpendaylightJournal.operation.in_(operation))
        else:
            q = q.filter(models.OpendaylightJournal.operation == operation)
    return session.query(q.exists()).scalar()

上述的2个机制分别位于2个类中,实现了描述的功能
Journal Entry架构:class OpendaylightJournalThread(object):
Entry恢复机制:class MaintenanceThread(object):
而L2和L3 的转发分别位于以下2个类中:

1
2
3
4
5
6
7
8
class OpenDaylightL2gwDriver(service_drivers.L2gwDriver):

class OpenDaylightL3RouterPlugin(
        common_db_mixin.CommonDbMixin,
        extraroute_db.ExtraRoute_db_mixin,
        l3_dvr_db.L3_NAT_with_dvr_db_mixin,
        l3_gwmode_db.L3_NAT_db_mixin,
        l3_agentschedulers_db.L3AgentSchedulerDbMixin):

在性能方面,ODL-Networking对对entry做cache缓存,有timeout和value属性,应该会有不小的性能提升
class CacheEntry(collections.namedtuple(‘CacheEntry’, [‘timeout’, ‘values’])):
为了更好的支持ODL增加的北向接口,ODL-Networking还特定会起一个start_odl_websocket_thread,位于下面的类中
class OpendaylightWebsocketClient(object):

参考文献

https://docs.openstack.org/developer/networking-odl/

蒋暕青

蒋暕青
Lots of mountains to climb,lots of enemies to defeat.

OpenStack cinder mutil-attach技术探秘

OpenStack cinder mutil-attach技术探秘 Continue reading

OpenStack octavia简介

Published on July 18, 2018

Openstack Cyborg项目介绍

Published on July 15, 2018