基于OVSDB协议在SDN控制器ONOS开发配置管理OVS API

介绍

目前protocol 中ovsdb协议层面已经实现,但是基于ovsdb protocol的上层接口并不完善,目前ONOS只支持以下几种

  • 网桥操作 boolean addBridge(BridgeDescription bridgeDescription); void deleteBridge(BridgeName bridgeName); Collection getBridges();
  • 端口操作 void addPort(BridgeName bridgeName, String portName); void deletePort(BridgeName bridgeName, String portName); Collection getPorts(); Set getPortNumbers(); List getLocalPorts(Iterable ifaceIds);
  • Controller操作 List getControllers(); void setControllers(List controllers);
  • 接口操作 boolean addPatchMode(String ifaceName, PatchDescription patchInterface); boolean removePatchMode(String ifaceName); boolean addTunnelMode(String intf, TunnelDescription tunnelDesc); boolean removeTunnelMode(String intf);

准备工作

增加api配置ovs,首先需要熟悉ovsdb rfc7047内容,其中规定了协议内容,如何操作ovsdb数据库,有哪些限制这一点非常重要,不然可能排查bug时很难找到其中原因。

另外还需要熟悉ovsdb数据库各表的各个字段以及属性,这个可以从ovs-vswitchd.conf.db.5.pdf中查找或者在数据模板/usr/share/openvswitch/vswitch.ovsschema中查看,不需记忆;

需要值得一提的对于写操作,下发的transcat string不能出现字段类型为immutable类型的column值,例如:可以vswitch.ovsschema查看到Interface Name字段是不可更改的,这个字段在通过ovsdb协议下发update或mutate操作,不能再value字段中携带,否则会导致更新失败。这个后面有举例说明。 这里写图片描述

开发步骤

Api框架图 这里写图片描述 基于ONOS整体框架良好的封装特性,在ONOS增加对底层接口配置特性,可分为以下四个步骤

  1. 在onos-api模块中找到所属分类,增加接口,BridgeConfig,ControllerConfig,InterfaceConfig,PortAdmin,PortConfig,PowerConfig,QueueConfig这些配置类是我们需要关注的,在onos api这一层,并没有暴露很多细节,这里它只是定义一个接口参数和返回类型,至于底层如何实现,使用ovsdb还是netconf它并不关心,只要匹配对应的driver就可以了
  2. 在driver层实现1步骤中的接口,比如现在BridgeConfig中的接口目前只有ovsdbBridgeConfig一种实现,而ControllerConfig中的接口有若干种实现; 这里写图片描述 以ovsdbBridgeConfig为例,它会获取一个当前设备的ovsdb协议连接,然后调用ovsdb协议封装的API进行具体实现,所有的driver接口实现都类似,另外需要注意,因为java接口implements关系,如果一个onos api在driver有多种实现,除了要实现特定协议的处理流程,其他类型协议需要打桩处理,否则编译出错;另外如果新增driver接口,比如新增ovsdbQueueConfig Driver,需要在onos/drivers/ovsdb/src/main/resources/ovsdb-driver.xml增加对应配置,否则behavior执行时会提示not support。 这里写图片描述 3. 第二步骤中提到的client中的接口实际上是ovsdb协议实现封装的一层对外API,这一步就需要在这里增加接口,这里增加的接口和第一步中的api类似的,只不过第一步中api是屏蔽底层细节的,底层可以是任何协议,而这一步的api是具体某种协议的api,ovsdb协议的api位置在onos\protocols\ovsdb\api,所有对外接口放在OvsdbClientService抽象接口中;另外其中的一些测试实现类也需要打桩处理。 4. 实现第三步骤中定义的接口,这里就是调用协议接口,来实现自己想要的功能,以创建tunnel隧道接口为例 这里写图片描述 这里面其实是对数据库的操作,最终会通过ovsdb协议下发到openvswitch,其中的操作适合ovsdb rfc定义是一致的 具体每个表的操作,可以到onos\protocols\ovsdb\rfc\src\main\java\org\onosproject\ovsdb\rfc\table中去参考,其中的表属性是和OpenvSwitch一一对应的 这里写图片描述

问题排查

按照上述流程开发,所有的ovs操作都是可以支持的,如果遇到问题,改如何排查? 大致可分为两种:1、查看onos log ,如果log中有导致该问题的异常,那么这个问题就比较容易解决,解决掉异常即可 2、如果onos log中没有异常,但是执行配置操作,转发平面ovs没有任何反应,没有看到想要的配置下发,例如onos执行qos的配置,但是转发面没有出现任何相关的配置,而onos也没有报任何错误。这个时候可能就是下发ovsdb协议可能违背了一些限制,导致更新失败,但是onos并不会报违背了哪些限制导致更新失败,如何知道具体什么原因导致的配置失败呢?答案是使用ovsdb-client –v transact命令模拟onos下发ovsdb协议配置操作,在ovs这个地方如果因为某种类型不匹配,它会打印出详细错误信息 但是ovsdb-client –v transact [SERVER] TRANSACTION这条命令的TRANSACTION本身很复杂,手工敲可能不太现实,这个TRANSACTION可以在onos 代码加log打印出来,如下图: 这里写图片描述 transactString就是最终要下发的操作,将这个打印出来,然后使用ovsdb-client命令验证下发操作失败的具体原因,需要强调的是transactString的内容是和rfc7047协议规定吻合的。 以下发ingress policing rate/burst为例,打印出来如下:

1
transact string{"id":"e676f626-4e80-45cb-ae45-2f5500b016d6","method":"transact","params":["Open_vSwitch",{"op":"update","row":{"ingress_policing_rate":["set",[100]],"ingress_policing_burst":["set",[100]]},"where":[["_uuid","==",["uuid","58dfdd10-43b9-45c7-aca6-e6fe6b5b6a7b"]]],"table":"Interface"}]}

这个时候就可以使用ovsdb-client验证其正确性,

1
2
3
4
5
6
7
8
[root@localhost ~]# ovsdb-client -v transact "[\"Open_vSwitch\",{\"op\":\"update\",\"row\":{\"ingress_policing_rate\":[\"set\",[200]],\"ingress_policing_burst\":[\"set\",[200]]},\"where\":[[\"_uuid\",\"==\",[\"uuid\",\"58dfdd10-43b9-45c7-aca6-e6fe6b5b6a7b\"]]],\"table\":\"Interface\"}]"
## 下面是执行上面命令打印的信息
[root@localhost ~]# ovsdb-client -v transact "[\"Open_vSwitch\",{\"op\":\"update\",\"row\":{\"ingress_policing_rate\":[\"set\",[200]],\"ingress_policing_burst\":[\"set\",[200]]},\"where\":[[\"_uuid\",\"==\",[\"uuid\",\"58dfdd10-43b9-45c7-aca6-e6fe6b5b6a7b\"]]],\"table\":\"Interface\"}]"
2016-09-08T06:29:50Z|00001|jsonrpc|DBG|unix:/var/run/openvswitch/db.sock: send request, method="transact", params=["Open_vSwitch",{"where":[["_uuid","==",["uuid","58dfdd10-43b9-45c7-aca6-e6fe6b5b6a7b"]]],"table":"Interface","op":"update","row":{"ingress_policing_burst":["set",[200]],"ingress_policing_rate":["set",[200]]}}], id=0
2016-09-08T06:29:50Z|00002|poll_loop|DBG|wakeup due to 0-ms timeout
2016-09-08T06:29:50Z|00003|poll_loop|DBG|wakeup due to [POLLIN] on fd 3 (<->/var/run/openvswitch/db.sock) at lib/stream-fd.c:155
2016-09-08T06:29:50Z|00004|jsonrpc|DBG|unix:/var/run/openvswitch/db.sock: received reply, result=[{"count":1}], id=0
[{"count":1}]

证明有一个row更新了,这个说明transaction是正确的,如果是不正确的呢,比如在上面的transaction中row中增加一个name字段,它就会提示name字段不可更改

1
2
3
4
5
6
[root@localhost ~]# ovsdb-client -v transact "[\"Open_vSwitch\",{\"op\":\"update\",\"row\":{\"name\":\"vnet0\",\"ingress_policing_rate\":[\"set\",[200]],\"ingress_policing_burst\":[\"set\",[200]]},\"where\":[[\"_uuid\",\"==\",[\"uuid\",\"58dfdd10-43b9-45c7-aca6-e6fe6b5b6a7b\"]]],\"table\":\"Interface\"}]"
2016-09-08T06:34:52Z|00001|jsonrpc|DBG|unix:/var/run/openvswitch/db.sock: send request, method="transact", params=["Open_vSwitch",{"where":[["_uuid","==",["uuid","58dfdd10-43b9-45c7-aca6-e6fe6b5b6a7b"]]],"table":"Interface","op":"update","row":{"name":"vnet0","ingress_policing_burst":["set",[200]],"ingress_policing_rate":["set",[200]]}}], id=0
2016-09-08T06:34:52Z|00002|poll_loop|DBG|wakeup due to 0-ms timeout
2016-09-08T06:34:52Z|00003|poll_loop|DBG|wakeup due to [POLLIN] on fd 3 (<->/var/run/openvswitch/db.sock) at lib/stream-fd.c:155
2016-09-08T06:34:52Z|00004|jsonrpc|DBG|unix:/var/run/openvswitch/db.sock: received reply, result=[{"syntax":"{\"op\":\"update\",\"row\":{\"ingress_policing_burst\":[\"set\",[200]],\"ingress_policing_rate\":[\"set\",[200]],\"name\":\"vnet0\"},\"table\":\"Interface\",\"where\":[[\"_uuid\",\"==\",[\"uuid\",\"58dfdd10-43b9-45c7-aca6-e6fe6b5b6a7b\"]]]}","details":"Cannot update immutable column name in table Interface.","error":"constraint violation"}], id=0
[{"details":"Cannot update immutable column name in table Interface.","error":"constraint violation","syntax":"{\"op\":\"update\",\"row\":{\"ingress_policing_burst\":[\"set\",[200]],\"ingress_policing_rate\":[\"set\",[200]],\"name\":\"vnet0\"},\"table\":\"Interface\",\"where\":[[\"_uuid\",\"==\",[\"uuid\",\"58dfdd10-43b9-45c7-aca6-e6fe6b5b6a7b\"]]]}"}]

另外删除Ovsdb Table时需要考虑到各表之间的依赖关系,对于有strong reference关系的表,在删除子表时需要先将父表的引用删除;对于调用deleteConfig接口时,这个接口会根据传入的参数查找父表的引用进行删除,这里特别要注意附表引用的数据类型,以Port、QoS与Queue关系为例。 这里写图片描述

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
"Port": {
     "columns": {
       "name": {
         "type": "string",
         "mutable": false},
       
       "qos": {
         "type": {"key": {"type": "uuid",
                          "refTable": "QoS"},
                  "min": 0, "max": 1}},
"QoS": {
     "columns": {
       "type": {
         "type": "string"},
       "queues": {
         "type": {"key": {"type": "integer",
                          "minInteger": 0,
                          "maxInteger": 4294967295},
                  "value": {"type": "uuid",
                            "refTable": "Queue"},
                  "min": 0, "max": "unlimited"}},
		

这里Port引用Qos的时候,单数据接口type为uuid,Qos引用Queue时的数据类型为map类型,既存在key又有value;关于表元素的数据类型,可以在vswitch.ovsschema的定义中找到

1
2
3
4
## 错误的删除Queue元素的操作
ovsdb-client -v transact "[\"Open_vSwitch\",{\"op\":\"mutate\",\"where\":[[\"queues\",\"includes\",[\"uuid\",\"dd2de53c-e4e7-4252-a191-72968a0f7859\"]]],\"mutations\":[[\"queues\",\"delete\",[\"uuid\",\"dd2de53c-e4e7-4252-a191-72968a0f7859\"]]],\"table\":\"QoS\"},{\"op\":\"delete\",\"where\":[[\"_uuid\",\"==\",[\"uuid\",\"dd2de53c-e4e7-4252-a191-72968a0f7859\"]]],\"table\":\"Queue\"}]"
## 正确
ovsdb-client -v transact "[\"Open_vSwitch\",{\"op\":\"mutate\",\"where\":[[\"queues\",\"includes\",[\"map\",[[0,[\"uuid\",\"dd2de53c-e4e7-4252-a191-72968a0f7859\"]]]]]],\"mutations\":[[\"queues\",\"delete\",[\"map\",[[0,[\"uuid\",\"dd2de53c-e4e7-4252-a191-72968a0f7859\"]]]]]],\"table\":\"QoS\"},{\"op\":\"delete\",\"where\":[[\"_uuid\",\"==\",[\"uuid\",\"dd2de53c-e4e7-4252-a191-72968a0f7859\"]]],\"table\":\"Queue\"}]"

OpenvSwitch transact命令 example 设置interface rate, mutate采用insert操作失败,采用+=可以

1
2
3
4
5
6
ovsdb-client -v transact "[\"Open_vSwitch\",{\"op\":\"mutate\",\"where\":[[\"_uuid\",\"==\",[\"uuid\",\"4aa0e983-29fd-4661-a05f-623e60fdf8e2\"]]],\"mutations\":[[\"ingress_policing_rate\",\"+=\",200]],\"table\":\"Interface\"}]"

## 设置interface rate, update操作,去掉interface name不可更新的column
ovsdb-client -v transact "[\"Open_vSwitch\",{\"op\":\"update\",\"row\":{\"mac_in_use\":[\"set\",[\"fe:54:00:78:6d:17\"]],\"duplex\":[\"set\",[\"full\"]],\"admin_state\":[\"set\",[\"up\"]],\"error\":[\"set\",[]],\"type\":\"\",\"ingress_policing_burst\":[\"set\",[100]],\"cfm_mpid\":[\"set\",[]],\"mac\":[\"set\",[]],\"cfm_fault\":[\"set\",[]],\"cfm_flap_count\":[\"set\",[]],\"options\":[\"map\",[]],\"cfm_health\":[\"set\",[]],\"ingress_policing_rate\":[\"set\",[100]],\"bfd\":[\"map\",[]],\"ifindex\":[\"set\",[36]],\"ofport\":[\"set\",[6]],\"other_config\":[\"map\",[]],\"bfd_status\":[\"map\",[]],\"cfm_remote_mpids\":[\"set\",[]],\"cfm_fault_status\":[\"set\",[]],\"link_speed\":[\"set\",[10000000]],\"external_ids\":[\"map\",[[\"vm-id\",\"e570935c-693a-458c-8a59-ca687e0473a3\"],[\"attached-mac\",\"52:54:00:78:6d:17\"],[\"iface-status\",\"active\"],[\"iface-id\",\"2cf8fa66-3b07-4db1-8e0b-ec635af99237\"]]],\"mtu\":[\"set\",[1500]],\"lacp_current\":[\"set\",[]],\"link_resets\":[\"set\",[1]],\"cfm_remote_opstate\":[\"set\",[]],\"link_state\":[\"set\",[\"up\"]],\"ofport_request\":[\"set\",[]],\"lldp\":[\"map\",[]],\"statistics\":[\"map\",[[\"tx_packets\",1732],[\"tx_errors\",0],[\"collisions\",0],[\"tx_dropped\",0],[\"rx_dropped\",0],[\"rx_crc_err\",0],[\"rx_bytes\",0],[\"rx_frame_err\",0],[\"rx_over_err\",0],[\"tx_bytes\",140292],[\"rx_errors\",0],[\"rx_packets\",0]]],\"status\":[\"map\",[[\"driver_name\",\"tun\"],[\"driver_version\",\"1.6\"],[\"firmware_version\",\"\"]]]},\"where\":[[\"_uuid\",\"==\",[\"uuid\",\"4aa0e983-29fd-4661-a05f-623e60fdf8e2\"]]],\"table\":\"Interface\"}]"

ovsdb-client -v transact "[\"Open_vSwitch\",{\"op\":\"update\",\"row\":{\"ingress_policing_rate\":[\"set\",[200]],\"ingress_policing_burst\":[\"set\",[200]]},\"where\":[[\"_uuid\",\"==\",[\"uuid\",\"58dfdd10-43b9-45c7-aca6-e6fe6b5b6a7b\"]]],\"table\":\"Interface\"}]"