一 、 摘要
本文主要针对 Openflowjava 部分进行实例简述,初学者需要对 java 了解一些,总结一些我自己的学习收获,不足之处请指正。
Openflowjava工程作为 Opendaylight 南向接口的协议栈存在,与openflowplugin 工程及外部的 netty.io 网络库紧密联系。其主要作用是接受南向接口上报的消息、解码、将其交给 Openflowplugin以便进一步上报以及接收Openflowplugin传达的发送消息的指令并将其编码为字节流从南向接口中发出。结构上与功能相关的是Openflow-protocol-api及Openflow-protocol-impl两个文件夹下的代码。 前者中用一系列yang文件定义了控制器支持的Openflow消息结构及对应的收、发行为,后者包含了消息的解码、编码及上报、下发的功能逻辑。
netty.io网络库在Opendaylight中负责处理控制器与switch间的TCP连接。控制器接收上报的Openflow消息时,消息的二进制字节流会被装入netty的Bytebuf类对象中传递给Openflowjava来提供解码,发送消息时,消息相关的数据在编码序列化后被封装进Bytebuf类中交给netty完成发送。Openflowplugin作为opendaylight处理openflow消息的外挂组件进一步负责解码后消息的分类、处理、上传以及发送消息时控制器指令数据的分发、消息体数据的组织与打包。Openflowplugin控制着openflow消息收发的流程和逻辑,openflowjava作为其末端动作的执行者存在。
二 、Openflow-protocol-api部分开发
openflow-protocol-api 文 件 夹 下 的 代 码 主 要 是 openflow-protocol-api/src/main/yang 路径下的一系列 yang 文件,yang 是一种表示结构与属性的语言,同时也是一个 RFC 标准(RFC6020) 。Opendaylight 使用这种语言来定义其所支持的 openflow 消息结构以及消息的收发动作。在项目编译阶段,maven 会调用 opendaylight 的 yangtools 工具根据 yang 文件生成一系列 java 文件,包括各openflow 消息的容器类、 容器类的构造器以及侦听并接收这些容器类的接口。 通过 yang 文件,我们可以简单清晰地定义我们需要的消息类型以及其上传/下发管道。
在上述路径下最为重要的是openflow-protocol.yang及openflow-types.yang两个文件,前者定义了 openflow 消息和动作,后者包含了自定义、非 yang 语言内置的特殊数据类型。此目录下我们主要进行 yang 文件的改写,将一些协议的字段写成 yang 的形式,主要进行 openflow-protocol.yang 与 openflow-types.yang 两个 model 中的协议添加。
在 openflow-protocol.yang 文件中,grouping 语句用于定义一个数据结构块,uses 语句可将其他 grouping 结构块复用进当前结构块。通过这种方法可定义对应于所有 openflow 消息的数据结构块。但这些结构快并非对 openflow 消息中所有字段的映射。如果这是一个从南向接口接受的消息,则结构快中只需要包含希望从消息获取并进一步上报的字段。如果是发送的消息,其结构快中只需要包含那些来自于控制器指令的数据。而 length、padding 等字段则不需要包括。也就是说,yang 文件中消息的定义是对实际的openflow 消息中有用数据的一种抽象。下面是 yang 文件中对 flow-stats 消息的数据结构块定义。
在定义了消息结构后,还需要在同一 yang 文件内定义消息的行为。相关的语句是 notification 和 rpc。notification 用于定义控制器被动等待收取的消息的收取行为。rpc 用于定义控制器主动发送的消息的发送行为以及可能的switch 应答消息的收取行为。如使用 notification 定义的 hello 消息的收取行为是:
其中 uses hello 是复用了之前定义的 hello 消息结构块(hello 命名的grouping) 。而使用 rpc 定义的有应答的 echo 消息的发送和收取行为是:
其中 input 语句块是发送的定义,output 语句块是收取的定义,uses 语句后的是之前定义的对应消息的结构块。利用上述的 yang 语法我们就可以定义各个 openflow 消息的结构和行为了。在 openflow-protocol-api 目录或 openflowjava 根目录下用 maven 完成项目编译后在 openflow-protocol-api/target/generated-sources/sal 路径下可以找到 yang 文件生成的消息结构相关的 java 文件。他们将会在编解码中被用到。Yang 和 java 之间的对应生成关系详见 opendaylight 的如下wiki页面。
三 、 Openflow- - protocol- - impl 部分
该文件夹下的开发主要针对消息的编解码模块以及上报/下发逻辑。先讲解码模块及逻辑流程。解码模块位于路径Openflow protocolimpl/src/main/java/org/opendaylight/openflowjava/protocol/impl/deserialization
TypeToClassMapInitializer.java 和 MessageDeserializerInitializer.java两个文件。它们初始化两张表,前者包含了 openflow 消息 type 值到消息类型的映射关系, 后者包含了消息 type 值、 消息类型和对应的解码模块间的映射关系。
根据这两张表,可以将 netty 传递来的包含了 openflow 消息二进制字节流的Bytebuf 对象传递给对应消息类型的解码模块。如果对 opendaylight 的消息有增删修改的话, 需要相应地调整这两张表, 同事 import 语句也需要做相应调整。解码模块位于路径openflow-protocol-impl/src/main/java/org/opendaylight/openflowjava/protocol/impl/deserialization/factories下,每一个java文件对应一种消息。文件中的java类继承 OFDeserializer和DeserializerRegistryInjector两个类,解码过程由其中的deserialize方法实现。这个方法接受 netty 传来的一个Bytebuf类实例rawmessage作为参数,在内部使用 yang 文件生成的该类型消息的的构造器及消息子结构的构造器(xxbuilder)以及 Bytebuf 类的方法进行解码。具体来说,使用 Bytebuf 的一系列 read 方法线性地将 rawmessage 中存储的二进制字节流中的各消息字段读出来,然后使用消息构造器或消息子结构构造器的 set 函数写入到消息构造器的各实例域中。每调用一次 rawmessage 的 read 方法,rawmessage 中指示读取位置的一个游标就会向后移动相应位数,以便下次调用时读取下一个字段。读取和写入的顺序按照消息中字段的顺序。 如此将一条消息中所有有用的字段都读取并赋给消息的构造器(xxbuilder) ,最终构造器使用自己的 build 方法返回作为解码结果的类实例。这个类的定义同样是由 yang 文件编译生成的。Hello 消息的示例代码如下:
解码的结果——一个类实例将被传递给 connectionadapter 模块, 该模块的实现位于
openflow-protocol-impl/src/main/java/org/opendaylight/openflowjava/protocol/impl/connection/ConnectionAdapterImpl.java 文件中, 其中一系列以 Future<RpcResult<>>为返回值的函数是 yang 中使用 rpc 定义的消息动作的收发实现,而 consume 方法是 notification 定义的消息动作的实现。先看 consume 方法,解码的结果——一个类实例作为参数 message 传递进来,在方法内部使用一系列 if 语句判断该 message 属于何种消息的类, 一旦匹配则传递给 messageListener 类中相应的on方法(这些 on 方法是根据 yang 文件中的notification语句生成的),之后message 就被传递到 openflowplugin 中去了。示例如下:
编码模块位于路径openflow-protocol-impl/src/main/java/org/opendaylight/openflowjava/protocol/impl/serialization 下,其中的MessageFactoryInitializer.java 用于初始化一张表,表示将要发送的消息类型与其编码模块间的映射关系。Openflowjava 得到openflowplugin 传递来的消息类实例调用 connectionadapterimpl 中的那些以Future<RpcResult<>>为返回值的函数并通过这张表找到需要的编码模块。实际的编码工作在路径openflow-protocol-impl/src/main/java/org/opendaylight/openflowjava/protocol/impl/serialization/factories 下的 java 文件中展开。这些类继承 OFSerializer<>类,其中的 serialize 方法以 openflowplugin 传递来的 xxinput 类实例 message 和Bytebuf 类实例 outBuffer 为参数。将 message 中的实例域提取出来传递给
outBuffer,调用 outBuffer 的一系列 write 方法将其以二进制字节流形式写入outBuffer 中,写入的顺序按照 openflow 消息的定义,其中也可能包括消息子结构的编码,类似于解码中的子结构解码。除了 message 中包含的字段外,还需要根据情况填写 length、 padding 等字段。填写完毕后 outBuffer 由 netty 完成对外发送,编码的流程就此结束。至此 openflowjava 工程中的工作结束。
四 、 总结
以上是关于 openflowjava 部分的开发文档简述, 具体的消息类型或协议可以自行进行添加和修改,本人知识水平有限,不足之处还请谅解。
本篇文章由OpenDaylight群(194240432)@北邮-Kobe提供,欢迎共同探讨。