文章目录

  • 1 开发环境介绍
  • 2 构建工程
    • 2.1 生成项目骨架
    • 2.2 编译工程
    • 2.3 启动控制器
    • 2.4 导入项目到开发IDE中
  • 3 实例开发
    • 3.1 RPC和YANG模型
      • 3.1.1 什么是RPC
      • 3.1.2 什么是YANG模型
    • 3.1 YANG模型中定义RPC
    • 3.2 RPC实现
      • 3.2.1 编写服务提供端service代码
      • 3.2.2 完善相关依赖
  • 4 RPC注册
  • 5 RPC测试
    • 5.1 启动服务
    • 5.2 调用RPC

1 开发环境介绍

1.系统环境:Ubuntu

2.软件环境:
jdk 1.8
maven 3.3.9
OpenDaylight
Mininet
Eclipse/IntelliJ IDEA

2 构建工程

2.1 生成项目骨架

根据OpenDaylight官方文档提供的Maven命令生成项目骨架

mvn archetype:generate -DarchetypeGroupId=org.opendaylight.controller -DarchetypeArtifactId=opendaylight-startup-archetype -DarchetypeRepository=http://nexus.opendaylight.org/content/repositories/opendaylight.release/ -DarchetypeCatalog=remote -DarchetypeVersion=1.5.1

构建成功后会在当前目录生成项目文件夹,进入到文件夹下即可看到工程目录结构

2.2 编译工程

对构建好的工程进行编译

mvn clean install -DskipTests -Dmaven.javadoc.skip=true -Dcheckstyle.skip=true

2.3 启动控制器

启动控制器,具体启动如下图

2.4 导入项目到开发IDE中

3 实例开发

3.1 RPC和YANG模型

3.1.1 什么是RPC

RPC 全称 Remote Procedure Call——远程过程调用。在学校学编程,我们写一个函数都是在本地调用就行了。但是在互联网公司,服务都是部署在不同服务器上的分布式系统,如何调用呢?
RPC技术简单说就是为了解决远程调用服务的一种技术,使得调用者像调用本地服务一样方便透明。

OpenDaylight采用RPC这种通信方式主要是基于以下几点:

  1. 可以实现跨语言跨平台调用,不同业务系统都可以进行调用OpenDaylight对外提供的服务,使得OpenDaylight服务更具通用性;
  2. 每个调用方只需要提出需求,不需要了解OpenDaylight具体的代码逻辑实现;
  3. RPC易于拓展,易于复用。

3.1.2 什么是YANG模型

早在2003年,IETF成立了一个NETCONF工作组,提出一种基于XML的网络配置管理协议,也就是NETCONF(Network Configuration Protocol),因为该协议的配置功能非常强大,所以广泛采用来配置网络。

NETCONF协议分为传输层、RPC层、操作层和内容层。其中,内容层是唯一没有标准化的层,于是一种新的建模语言YANG产生了,它的目标是对NETCONF数据模型、操作进行建模,覆盖NETCONF协议的操作层和内容层。


YANG模型的产生主要是对以下三类数据进行建模:

  1. 远程过程调用(rpc):对可在网元上触发的远程程序调用进行建模;
  2. 资源(data):对要操控的资源进行建模,也就是对网元设备的的configure或者operational数据进行建模;
  3. 通知(notification):对网元发出的事件通知消息进行建模。

OpenDaylight控制器目前主要采用MD-SAL架构,这里的M指的就是YANG 模型。在开发过程中,一般先根据需求先设计YANG模型来确定准备提供哪些数据和服务,然后使用Maven进行编译时,通过YANG Tools插件将YANG文件的转译为相应的java类、接口。开发者通过实现自动生成的Java代码来实现具体的API和服务内容。

由于YANG文件通俗易懂,我们完全可以仅仅通过YANG文件就可以大概了解到系统提供的数据和服务内容,十分的方便。同时,OpenDaylight访问Data Store的API完全基于Yang模型,这部分代码Yang Tools插件自动生成,这样开发人员只需关注如何进行提供服务,大大提高了开发效率和降低了OpenDaylight控制器的开发难度。

3.1 YANG模型中定义RPC

  1. 编辑api/src/main/yang目录下的topology.yang,进行yang文件编写,完整的topology.yang文件如下:

    module topology {yang-version 1;namespace "urn:opendaylight:params:xml:ns:yang:topology";prefix "topology";import ietf-inet-types { prefix inet; revision-date 2013-07-15; }description "get links and ports information";revision "2018-03-07" {description "Initial revision of topology model";}grouping output-link{leaf link-id{type inet:uri;description "link id";}leaf src-device{type inet:uri;description "source device";}leaf src-port{type int32;description "source port";}leaf dst-device{type inet:uri;description "destination device";}leaf dst-port{type int32;description "destination port";}}grouping output-port{leaf device-id{type inet:uri;description "link id";}leaf port-number{type string;description "port number";}leaf port-name{type string;description "port name";}leaf hardware-address{type string;description "hardware address";}leaf current-speed{type int64;description "current speed";}leaf maximum-speed{type int64;description "maximum speed";}leaf link-down{type boolean;description "link down";}}rpc list-links-info{output {list links-info{uses output-link;description "link info";}}}rpc list-ports-info{output{list ports-info{uses output-port;description "port info";}}}}
    

    解析:
    ①module:在实际项目中,一个工程中YANG模型很多,所以可以通过module来划分不同模块的YANG。各个module描述各自的数据模型,不同module之间可以互相引用,十分灵活。

    ②namespace和prefix :每个module有一个独立的namespace以避免命名冲突,同时还有一个简称prefix。

    ③import:通过import导入外部模块,可以在本模块中使用导入模块中定义的数据结构或数据类型。导入时会给外部模块起一个prefix,引用导入模块中的内容时以prefix开头即可。

    ④description:描述YANG文件的功能或者各个节点的功能。

    ⑤revision:版本信息。

    ⑥grouping:一种可重用的schema nodes的集合。该集合可能在定义处的module中被使用,也可能在包含它的modules中使用,还可能在导入它的modules中被使用。grouping不会生成任何节点,可以看成一种临时树干。

    ⑦leaf:一种数据节点,在data tree中最多只能有一个实例存在。一个leaf节点只能有一个值,并且不能有子节点。leaf主要用来定义属性值。

    ⑧type:指定leaf的数据类型,该类型可以是YANG RFC规定的基本类型,也可以是自定义的派生类型。在YANG文件中,int32为基本类型,inet:uri为派生类型,派生类型的定义由typedef定义。基本的原生数据类型有uint8、uint16、string、bits、binary、boolean等等。

    ⑨rpc:远程过程调用。它可能包含input和output子节点,分别是该rpc所需要的输入和输出数据结构。若没有则表明该操作不需要输入数据或者没有输出数据。

    ⑩uses:实例化在grouping声明中定义的schema nodes。

    更为详细的YANG语法知识可以参考rfc 6020(https://tools.ietf.org/html/rfc6020)

    需要注意的是,由于在YANG文件中引入了ietf-inet-types,需要在api目录下的pom.xml中添加相关依赖:

    <dependencies><dependency><groupId>org.opendaylight.mdsal.model</groupId><artifactId>ietf-inet-types-2013-07-15</artifactId><version>1.2.2-Carbon</version></dependency></dependencies>
    

    Tip:dependencies标签如果存在则直接添加dependency这一层标签即可。

  2. 定义好YANG文件,需要重新编译工程,编译时无需整体编译,只需进入到工程主目录下的api目录下进行编译即可,具体命令如下:

    cd api/
    mvn clean install -DskipTests -Dmaven.javadoc.skip=true -Dcheckstyle.skip=true
    
  3. 编译时,maven会通过Yang Tools插件生成基本的Java文件,生成的Java文件如下图所示:

3.2 RPC实现

在OpenDaylight中默认采用是DataStore这种数据库。DataStore采用树型结构进行数据存储,它是一种高效的In-Memory(内存)数据库。

DataStore中的数据存储分两种形式:config和operational。config持有由应用所写的数据,一般是设备的配置信息或者是需要持久化的数据,而operational反映了设备运行时的数据,表明设备的运行状态,从设备读取数据,如果没有错误即可以看到设备的当前实际信息。

config和operational相同之处在于两者的数据都以树型节点的形式存储在内存中;不同点是config数据生命周期比较长,控制器断电重启后数据会被持久化到硬盘中,持久化主要采用快照(snapshot)和增量日志(journal)这两种方式,而operational数据生命周期比较短,控制器断电重启后数据失效。

本案例中的全网拓扑信息以及获取网卡信息都是从OpenDaylight的operational数据库中读取出来的状态数据。

由于DataStore的内容比较多,我们小组将会在后面的系列文章中,对DataStore的知识进行深入探讨。

3.2.1 编写服务提供端service代码

impl/src/main/java/org/opendaylight/topology/impl/TopologyProvider.java文件进行代码实现,由于代码比较少,这里贴出TopologyProvider.java的所有代码(不包括package和import):

public class TopologyProvider implements TopologyService {private static final Logger LOG = LoggerFactory.getLogger(TopologyProvider.class);private final DataBroker dataBroker;private static final String FLOWID = "flow:1";public TopologyProvider(final DataBroker dataBroker) {this.dataBroker = dataBroker;}public void init() {LOG.info("TopologyProvider Session Initiated");}public void close() {LOG.info("TopologyProvider Closed");}@Overridepublic Future<RpcResult<ListLinksInfoOutput>> listLinksInfo() {final SettableFuture<RpcResult<ListLinksInfoOutput>> futureResult = SettableFuture.create();ListLinksInfoOutputBuilder outputBuilder = new ListLinksInfoOutputBuilder();final InstanceIdentifier.InstanceIdentifierBuilder<Topology> topologyId = InstanceIdentifier.builder(NetworkTopology.class).child(Topology.class, new TopologyKey(new TopologyId(new Uri(FLOWID))));InstanceIdentifier<Topology> topologyIId = topologyId.build();Topology topology = read(LogicalDatastoreType.OPERATIONAL, topologyIId);if (topology == null || topology.getLink() == null || topology.getLink().size() < 1) {futureResult.set(RpcResultBuilder.success(outputBuilder.build()).build());return futureResult;}List<LinksInfo> linkInfos = new ArrayList<>();topology.getLink().forEach(temp -> {LinksInfoBuilder lib = new LinksInfoBuilder();lib.setLinkId(temp.getLinkId()).setSrcDevice(temp.getSource().getSourceNode()).setDstPort(getPort(temp.getSource().getSourceTp().getValue())).setDstDevice(temp.getDestination().getDestNode()).setDstPort(getPort(temp.getDestination().getDestTp().getValue()));linkInfos.add(lib.build());});outputBuilder.setLinksInfo(linkInfos);futureResult.set(RpcResultBuilder.success(outputBuilder.build()).build());return futureResult;}@Overridepublic Future<RpcResult<ListPortsInfoOutput>> listPortsInfo() {final SettableFuture<RpcResult<ListPortsInfoOutput>> futureResult = SettableFuture.create();ListPortsInfoOutputBuilder listPortsInfoOutputBuilder = new ListPortsInfoOutputBuilder();Nodes nodes = queryAllNode(LogicalDatastoreType.OPERATIONAL);if (nodes == null || nodes.getNode() == null || nodes.getNode().size() < 1) {futureResult.set(RpcResultBuilder.success(listPortsInfoOutputBuilder.build()).build());return futureResult;}List<PortsInfo> portsInfos = new ArrayList<>();nodes.getNode().forEach(tempNode -> {List<NodeConnector> nodeConnectors = filterNodeConnectors(tempNode);if (nodeConnectors == null || nodeConnectors.size() < 1) {return;}nodeConnectors.forEach(tempPort -> {PortsInfoBuilder pi = new PortsInfoBuilder();FlowCapableNodeConnector augmentation = tempPort.getAugmentation(FlowCapableNodeConnector.class);pi.setDeviceId(tempNode.getId()).setPortNumber(new String(augmentation.getPortNumber().getValue())).setPortName(augmentation.getName()).setHardwareAddress(augmentation.getHardwareAddress().getValue()).setLinkDown(augmentation.getState().isLinkDown()).setMaximumSpeed(augmentation.getMaximumSpeed()).setCurrentSpeed(augmentation.getCurrentSpeed());portsInfos.add(pi.build());});});listPortsInfoOutputBuilder.setPortsInfo(portsInfos);futureResult.set(RpcResultBuilder.success(listPortsInfoOutputBuilder.build()).build());return futureResult;}private <D extends DataObject> D read(final LogicalDatastoreType store, final InstanceIdentifier<D> path) {D result = null;final ReadOnlyTransaction transaction = dataBroker.newReadOnlyTransaction();Optional<D> optionalDataObject;final CheckedFuture<Optional<D>, ReadFailedException> future = transaction.read(store, path);try {optionalDataObject = future.checkedGet();if (optionalDataObject.isPresent()) {result = optionalDataObject.get();} else {LOG.debug("{}: Failed to read {}", Thread.currentThread().getStackTrace()[1], path);}} catch (final ReadFailedException e) {LOG.warn("Failed to read {} ", path, e);}transaction.close();return result;}private int getPort(String tp) {return Integer.parseInt(tp.split(":")[2]);}private Nodes queryAllNode(LogicalDatastoreType configuration) {final InstanceIdentifier<Nodes> identifierNodes = InstanceIdentifier.create(Nodes.class);return read(configuration, identifierNodes);}private List<NodeConnector> filterNodeConnectors(Node node) {final List<NodeConnector> connectors = Lists.newArrayList();final List<NodeConnector> list = node.getNodeConnector();if (list != null && list.size() > 0) {for (final NodeConnector nodeConnector : list) {if (!nodeConnector.getId().getValue().endsWith("LOCAL")) {connectors.add(nodeConnector);}}}return connectors;}}

获取网络拓扑信息(listLinks方法)的大致流程如下:
①从network-topology的operational数据库中读取“flow:1”节点的所有内容。
②过滤“flow:1”下的节点内容,从而获取所需的link信息。
③将获取到的link信息封装成Future<RpcResult>进行返回给消费端。

获取网卡信息(listPortsInfo方法)的大致流程如下:
①从opendaylight-inventory中获取所有的Node节点,Node表示连接在控制器上的SDN交换机设备。
②获取每个Node节点的所有NodeConnector,然后获取需要的NodeConnector上的参数信息,NodeConnector相当于交换机的端口。
③将获取到信息封装成Future<RpcResult>进行返回给消费端。

3.2.2 完善相关依赖

我们在代码中使用了ietf-topology、model-inventory、model-flow-service,需要在impl目录的pom.xml文件中添加如下依赖。

<dependency><groupId>org.opendaylight.mdsal.model</groupId><artifactId>ietf-topology</artifactId><version>2013.10.21.10.2-Carbon</version></dependency><dependency><groupId>org.opendaylight.controller.model</groupId><artifactId>model-inventory</artifactId><version>1.5.2-Carbon</version></dependency><dependency><groupId>org.opendaylight.openflowplugin.model</groupId><artifactId>model-flow-service</artifactId><version>0.4.2-Carbon</version></dependency>

与之相对应的是需要在features目录下的src/main/features/features.xml文件添加引用仓库:

<repository>mvn:org.opendaylight.openflowplugin/features-openflowplugin/${openflowplugin.version}/xml/features</repository>

同时也需要在features.xml文件中的name为odl-topology-api的feature标签中添加如下feature:odl-openflowplugin-flow-services,该句添加位置如下:

<feature name='odl-topology-api' version='${project.version}' description='OpenDaylight :: topology:: api'><feature version='${mdsal.model.version}'>odl-mdsal-models</feature><feature version="${openflowplugin.version}">odl-openflowplugin-flow-services</feature><bundle>mvn:org.opendaylight.topology/topology-api/{{VERSION}}</bundle></feature>

其中openflowplugin.version在features目录下pom.xml文件中定义,需要在其中的properties标签内添加:

<openflowplugin.version>0.4.2-Carbon</openflowplugin.version>

4 RPC注册

编写完RPC实现的代码后,需要对RPC进行注册到OpenDaylight控制器以供外部调用,注册RPC方式有多种,后面的系列文章中会对RPC注册问题作详细叙述,本案例中采用的是Blueprint方式来注册RPC。

Blueprint是针对OSGi的依赖注入解决方案,用法非常类似Spring。当使用服务的时候,Blueprint会马上创建并注入一个代理(Proxy)。对这些服务进行调用时,如果服务在当前不可用的话,将会产生阻塞,直至能够获取到服务或超时。

其实就是我们把一些原本需要写代码去实现的内容用xml文件的方式简化了,这个xml文件固定放在bundle的src\main\resources\org\opendaylight\blueprint目录下,否则获取不到文件内容,至于文件名可以随意。可以想象到有专门去解析这个xml文件的代码,这个代码的位置就在controller\opendaylight\blueprint目录下,有兴趣的读者可以去看下源码。

具体操作过程:在impl/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml中添加:<odl:rpc-implementation ref=“provider”/>,其中provider是实例化类的id,id名称可以改变但必须两者保持一致。

impl-blueprint.xml文件内容如下:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"odl:use-default-for-reference-types="true"><reference id="dataBroker"interface="org.opendaylight.controller.md.sal.binding.api.DataBroker"odl:type="default" /><bean id="provider"class="org.opendaylight.topology.impl.TopologyProvider"init-method="init" destroy-method="close"><argument ref="dataBroker" /></bean><odl:rpc-implementation ref="provider"/></blueprint>

5 RPC测试

RPC测试需要用到OpenDaylight WEB控制台和l2switch相关组件。因此在编译项目之前,需要在features目录下的src/main/features/features.xml文件添加引用仓库:

<repository>mvn:org.opendaylight.l2switch/features-l2switch/${l2switch.version}/xml/features</repository>

5.1 启动服务

编译项目

mvn clean install -DskipTests -Dmaven.javadoc.skip=true -Dcheckstyle.skip=true./karaf/target/assembly/bin/karaf

启动完成后,可以在karaf控制台命令查看开发的bundle是否安装成功,如果显示Active表示安装成功,具体命令为:

5.2 调用RPC

使用OpenDaylight WEB控制台的Yangman进行调用RPC,登录到WEB控制台后,依次鼠标点击Yangman—>topology rev.2018.03.07—>operations

补充:ODL中RPC接口的restconf化原理
参考:https://blog.csdn.net/sunquan291/article/details/82494264

八、OpenDaylight应用基础开发(ODL控制器初级开发流程总结)相关推荐

  1. 【安卓开发 】Android初级开发(八)WebView网页

    1.网页的基本组成 2.WebView的常用方法 WebView webView = findViewById(R.id.webvv);//加载线上网页webView.loadUrl("ht ...

  2. 【安卓开发 】Android初级开发(十二)Android向系统日历中添加事件

    1.首先需要获取权限 <uses-permission android:name="android.permission.READ_CALENDAR" /><us ...

  3. 【安卓开发 】Android初级开发(十一)Android中多线程

    线程的创建 1.创建一个Thread类,或者创建一个Thread子的对象: 2.创建一个Runnable接口的类对象; 传入Runnable对象创建线程 package com.sina.baode; ...

  4. 【安卓开发 】Android初级开发(网络操作)

    URI部分 URI详情 uri的具体案例使用参考,app与网页之间的页面跳转 H5唤醒app并跳转到指定页面 H5打开APP技术总结 H5页面唤醒app的方法 Android配置Scheme使用浏览器 ...

  5. 【安卓开发 】Android初级开发(十)Android中app自动更新版本号比较

    //版本号比较:前者小返回true,前者大返回false public static boolean versionCompareTo(String version1, String version2 ...

  6. 【安卓开发 】Android初级开发(九)Android中封装View提供接口供点击事件回调的方法及使用

    自古一楼先上图 package com.example.mydialog;import android.app.Dialog; import android.content.Context; impo ...

  7. 【安卓开发 】Android初级开发(七)MD5加密

    //MD5加密public String encrypt(String raw){String md5Str = raw;try {MessageDigest md = MessageDigest.g ...

  8. 【安卓开发 】Android初级开发(六)Activity生命周期

    @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setC ...

  9. 【安卓开发 】Android初级开发(五)自定义View

    1.自定义View的构造函数调用的场景 package com.sina.myapplication;import android.content.Context; import android.ut ...

  10. 【安卓开发 】Android初级开发(四)ListView

    ListView的实现步骤 1.单独一行的布局可以如下 <?xml version="1.0" encoding="utf-8"?> <Lin ...

最新文章

  1. AI一分钟 | 传阿里暂停在美扩张云业务;Google发布AI工具以识别儿童性侵犯图片...
  2. BASH 进阶 文件描述符号的使用
  3. File-nodejs
  4. zcmu2117(普通母函数)
  5. python 跳一跳辅助_使用Python制作微信跳一跳辅助
  6. 微服务架构系列二:密码强度评测的实现与实验
  7. spring mvc学习(7):springmvc学习笔记(常用注解)
  8. Apache+Tomcat动静分离
  9. ADO.NET数据库应用开发_ExtendedProperties属性
  10. Bella团队正在进行Flex Saving v2上线最后的准备工作
  11. 嵌套 思维导图_看我怎么用思维导图,来轻松学习JavaScript,值得收藏
  12. mysqludf_json将关系数据以JSON编码
  13. 区块链技术指南pdf
  14. 飞思卡尔MC9S12G系列单片机flash擦写
  15. C语言英文打字训练程序(给出正确率,耗时)
  16. 502 bad gateway
  17. 搜狗输入法不错,附带的进程需要一个个把exe文件重命名
  18. 路由器自动获取ip失败
  19. 吃鸡账号显示连接不了服务器,《绝地求生》“与Steam服务器连接时出现了一个问题”怎么解决...
  20. 为什么打印还要另存为_为什么打印时会出现另存为保存文件

热门文章

  1. 押注AI大装置,商汤的“月亮与六便士”
  2. QT教程:QT的基本了解
  3. [NTUSTISC pwn LAB 2]栈溢出:gdb动态调试bof2
  4. 艰辛坎坷的360崛起之路
  5. Mac下导入virtualenv报错:its parent directory is not owned by the current user
  6. Web核心(Java技术栈、HTTP、Servlet、Request、Response)
  7. 为什么您没有得到最好的承包商,您将如何做(第2部分)
  8. 网吧万兆到桌面服务器配置,网吧篇:2019网吧组网及交换机推荐 万兆千兆到桌面...
  9. H3C设备网吧万兆光模块解决方案
  10. android 状态栏高度多少像素,Android之获取屏幕的尺寸像素及获取状态栏标题栏高度...